diff --git a/.babelrc b/.babelrc index 8726a848e2e..5d42055cabb 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,19 @@ { - "presets": ["es2015"], - "plugins": ["transform-object-assign", "transform-es3-property-literals", "transform-es3-member-expression-literals"] + "presets": [ + ["env", { + "targets": { + "browsers": [ + "chrome >= 61", + "safari >=8", + "edge >= 14", + "ff >= 57", + "ie >= 10", + "ios >= 8" + ] + } + }] + ], + "plugins": [ + "transform-object-assign" + ] } diff --git a/.eslintrc.js b/.eslintrc.js index 56081a88262..e6975951f06 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,6 +3,13 @@ module.exports = { "browser": true, "commonjs": true }, + "settings": { + "import/resolver": { + "node": { + "moduleDirectory": ["node_modules", "./"] + } + } + }, "extends": "standard", "globals": { "$$PREBID_GLOBAL$$": false @@ -21,12 +28,9 @@ module.exports = { // See Issue #1111. "camelcase": "off", "eqeqeq": "off", - "no-control-regex": "off", "no-return-assign": "off", "no-throw-literal": "off", "no-undef": "off", - "no-use-before-define": "off", "no-useless-escape": "off", - "standard/no-callback-literal": "off", } }; diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 61eb327fd2c..9fdb04ba556 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,6 +11,7 @@ Thank you for your pull request. Please make sure this PR is scoped to one chang - [ ] Refactoring (no functional changes, no api changes) - [ ] Build related changes - [ ] CI related changes +- [ ] Does this change affect user-facing APIs or examples documented on http://prebid.org? - [ ] Other ## Description of change @@ -32,6 +33,9 @@ Be sure to test the integration with your adserver using the [Hello World](/inte - contact email of the adapter’s maintainer - [ ] official adapter submission +For any changes that affect user-facing APIs or example code documented on http://prebid.org, please provide: + +- A link to a PR on the docs repo at https://github.com/prebid/prebid.github.io/ ## Other information diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000000..0925c69c703 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,19 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 14 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - bug + - feature +# 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 +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.travis.yml b/.travis.yml index 73b5de5ae4a..912f3de8b56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,28 @@ +sudo: required dist: trusty - language: node_js - node_js: - - "7.0" - -# See https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-the-Chrome-addon-in-the-headless-mode +- '7.0' +env: +- BROWSERSTACK_USERNAME=info184 addons: - chrome: stable - + chrome: stable, + browserstack: + username: info184 + access_key: + secure: Ru286R4pMcEIRKwb2AoaaJY6lEKIzeZraxY7CtbOP4ykNk7uqsnyitk4QwxpCCh0n35b71m30okW6ZmZnl0lJXhOMdYoSOYBAnUw2Vn7Y93oMSKIC5dc2/qmtF1t2b1qX65/Ont2iJUj+UY8VQw5Hk2vIT4/5wifYPBnV5ILK4AI7SVk/ma7OzK4rkp3WThlouddctAd7tx4O3YIyJKDi9lkfcMA0pnH4OZSOlDClRLIy50Q1NE+iyqHtWFZK1TwJ+IhQbSsCLbuyQJBRnyJJEftNmtrs5MCZt/9pwFDj7c8+o11F6HCsTBYFkehFRfbKnmhCc1G+bsNXY8OxIWwEHeuVmSGK7TDPYcPPQBc03mcQ1fY/IPNQOdsVJ/n8RsG2u0IU2CF2hhkuNFzeov7dOHljanc45NKOrLdjwzP1aZCAUvLquOTzvmdF23nJhMs8UO+Du4kTK5VfmKyz1MC91E40a0Q15+O4qmS39rNOlwhxPJSfuxxL1jKVPJ7PsFbTkGM8M/XPJ6dyGLufy225HjdLdJTAOa5BZ4st+nXH/AzqHzy6a2I5vTmAz1j4gHLgVU+iNxAkX8znb25s3Rs1ZuFVj+aBSBmNoQA1FA5f/uXWeruTdDig7ksp+XdjsjLm9Md8cWwYaEn04FYj1ztJrylrEMfnc0Kcs6zQ3fll1g= before_install: - - npm install -g gulp - - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost & - -script: - - gulp run-tests +- npm install -g gulp +- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost & +script: |- + if [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then + gulp test --browserstack + else + gulp run-tests + fi +notifications: + slack: + on_success: never + on_failure: always + on_pull_requests: false + secure: C4O77VtABLE5DiPDeKGqUcsBdTBMNjQRLc8iBfSp231e95K1rA/JXJJEQN/lVhhiFJyPhxueE0i6cR0zD8uAMC8HRShGGfPjEZ7f6glawPzap2wFwjAyVkknYV+BMKcX0jvn7CiSKBj+zTbHQfn/Uj3nMSbDZQIdbNDiFGh4NuDr3/Yd/efhsw/miExlSPSWqGVCKV3WPpTrU3BRpLNDq4sZMXP9ORZxGK7ER3tsMiD2z05YpvC+mibESJxaY0qsuQu1y1Gu65QLPe5ocw405btJwqYn+b4YFpUd2GbLNhjtLzsc+OKrD0DWuEI0bxePQUYDga5wR6g4cdZaXU3ixDjee7sJbDeVJAuykGlfZ4A1k+fQIgPs3s9XMHaeG9AfDhFiZ/UoNdonzos1iSa/Y1TzHIXp1wnbHKT5HUWWPFNb5PzJxHEtHbm3jwOH4iK8VAq94ec16M2aqUAj7muiqcrTlYa5rs6jRlXo/TRymFnbQRdBT7eLmLNDQD35yR1Y+4mxHqKi+3189yG9RE+uwIlB+9HZFgNbioOApB+jarKC6M0qEgn0bHxkpJBP8JmNCA84U0ZUzyPvuMGsRbisAmKoUsU8C6cq59QDfBTcCTvKXK6r+6f23iRGieoGSbTxYQj46QkykpbWU0WstQDQsZL3L316uZecOVZmWKBRxPs= diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5856835f785..dc8d80ec384 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,9 +48,6 @@ When you are adding code to Prebid.js, or modifying code that isn't covered by a - _Assert_: check that the expected results have occurred - e.g., use Chai assertions to check that the expected output is equal to the actual output - Test the public interface, not the internal implementation -- If using global `pbjs` data structures in your test, take care to not completely overwrite them with your own data as that may affect other tests relying on those structures, e.g.: - - **OK**: `pbjs._bidsRequested.push(bidderRequestObject);` - - **NOT OK**: `pbjs._bidsRequested = [bidderRequestObject];` - If you need to check `adloader.loadScript` in a test, use a `stub` rather than a `spy`. `spy`s trigger a network call which can result in a `script error` and cause unrelated unit tests to fail. `stub`s will let you gather information about the `adloader.loadScript` call without affecting external resources - When writing tests you may use ES2015 syntax if desired diff --git a/LICENSE b/LICENSE index c8daf0f8942..f5028c63317 100644 --- a/LICENSE +++ b/LICENSE @@ -176,18 +176,7 @@ END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2013 APPNEXUS INC + Copyright 2017 PREBID.ORG, INC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -199,4 +188,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/PR_REVIEW.md b/PR_REVIEW.md new file mode 100644 index 00000000000..012a2d8b501 --- /dev/null +++ b/PR_REVIEW.md @@ -0,0 +1,48 @@ +## Summary +We take PR review seriously. Please read https://medium.com/@mrjoelkemp/giving-better-code-reviews-16109e0fdd36#.xa8lc4i23 to understand how a PR review should be conducted. Be rational and strict in your review, make sure you understand exactly what the submitter's intent is. Anyone in the community can review a PR, but a Prebid Org member is also required. A Prebid Org member should take ownership of a PR and do the initial review. + +If the PR is for a standard bid adapter or a standard analytics adapter, just the one review from a core member is sufficient. The reviewer will check against [required conventions](http://prebid.org/dev-docs/bidder-adaptor.html#required-adapter-conventions) and may merge the PR after approving and confirming that the documentation PR against prebid.org is open and linked to the issue. + +For modules and core platform updates, the initial reviewer should request an additional team member to review as a sanity check. Merge should only happen when the PR has 2 `LGTM` from the core team and a documentation PR if required. + +### General PR review Process +- Checkout the branch (these instructions are available on the github PR page as well). +- Verify PR is a single change type. Example, refactor OR bugfix. If more than 1 type, ask submitter to break out requests. +- Verify code under review has at least 80% unit test coverage. If legacy code has no unit test coverage, ask for unit tests to be included in the PR. +- Verify tests are green in Travis-ci + local build by running `gulp serve` | `gulp test` +- Verify no code quality violations are present from linting (should be reported in terminal) +- 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. +- 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) + +### New Adapter or updates to adapter process +- Follow steps above for general review process. In addition, please verify the following: +- Verify that bidder has submitted valid bid params and that bids are being received. +- Verify that bidder is not manipulating the prebid.js auction in any way or doing things that go against the principles of the project. If unsure check with the Tech Lead. +- Verify that the bidder is being as efficient as possible, ideally not loading an external library, however if they do load a library it should be cached. +- Verify that code re-use is being done properly and that changes introduced by a bidder don't impact other bidders. +- If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed. +- If the adapter is triggering any user syncs make sure they are using the user sync module in the Prebid.js core. +- Requests to the bidder should support HTTPS +- Responses from the bidder should be compressed (such as gzip, compress, deflate) +- Bid responses may not use JSONP: All requests must be AJAX with JSON responses +- All user-sync (aka pixel) activity must be registered via the provided functions +- Adapters may not use the $$PREBID_GLOBAL$$ variable +- All adapters must support the creation of multiple concurrent instances. This means, for example, that adapters cannot rely on mutable global variables. +- Adapters may not globally override or default the standard ad server targeting values: hb_adid, hb_bidder, hb_pb, hb_deal, or hb_size, hb_source, hb_format. +- After a new adapter is approved, let the submitter know they may open a PR in the [headerbid-expert repository](https://github.com/prebid/headerbid-expert) to have their adapter recognized by the [Headerbid Expert extension](https://chrome.google.com/webstore/detail/headerbid-expert/cgfkddgbnfplidghapbbnngaogeldmop). The PR should be to the [bidder patterns file](https://github.com/prebid/headerbid-expert/blob/master/bidderPatterns.js), adding an entry with their adapter's name and the url the adapter uses to send and receive bid responses. + +## 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. +- 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. +- 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. +- If possible, check in on older items and see if they can be unstuck. diff --git a/README.md b/README.md index ac15a991f0c..1ec83859b48 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,7 @@ Working examples can be found in [the developer docs](http://prebid.org/dev-docs $ git clone https://github.com/prebid/Prebid.js.git $ cd Prebid.js - $ yarn install - -Prebid supports the `yarn` npm client. This is an alternative to using `npm` for package management, though `npm install` will continue to work as before. - -For more info, see [the Yarn documentation](https://yarnpkg.com). + $ npm install *Note:* You need to have `NodeJS` 4.x or greater installed. @@ -60,11 +56,8 @@ For example, when running the serve command: `gulp serve --modules=openxBidAdapt Building with just these adapters will result in a smaller bundle which should allow your pages to load faster. **Build standalone prebid.js** -Prebid now supports the `yarn` npm client. This is an alternative to using `npm` for package management, though `npm` will continue to work as before. - -For more info about yarn see https://yarnpkg.com -- Clone the repo, run `yarn install` +- Clone the repo, run `npm install` - Then run the build: $ gulp build --modules=openxBidAdapter,rubiconBidAdapter,sovrnBidAdapter @@ -82,11 +75,11 @@ With `modules.json` containing the following ] ``` -**Build prebid.js using Yarn for bundling** +**Build prebid.js using npm for bundling** -In case you'd like to explicitly show that your project uses `prebid.js` and want a reproducible build, consider adding it as an `yarn` dependency. +In case you'd like to explicitly show that your project uses `prebid.js` and want a reproducible build, consider adding it as an `npm` dependency. -- Add `prebid.js` as a `yarn` dependency of your project: `yarn add prebid.js` +- Add `prebid.js` as a `npm` dependency of your project: `npm install prebid.js` - Run the `prebid.js` build under the `node_modules/prebid.js/` folder $ gulp build --modules=path/to/your/list-of-modules.json @@ -113,6 +106,11 @@ To run the unit tests: ```bash gulp test ``` +To run tests for a single file: + +```bash +gulp test --file "path/to/spec/file.js" +``` To generate and view the code coverage reports: diff --git a/RELEASE_SCHEDULE.md b/RELEASE_SCHEDULE.md new file mode 100644 index 00000000000..f1e930b98b6 --- /dev/null +++ b/RELEASE_SCHEDULE.md @@ -0,0 +1,22 @@ +# 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. + +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. + +You can determine what is in a given build using the [releases page](https://github.com/prebid/Prebid.js/releases) + +Announcements regarding releases will be made to the #headerbidding-dev channel in subredditadops.slack.com. + +# FAQs + +**1. Is there flexibility in the 2-week schedule?** + +If a major bug is found in the current release, a maintenance patch will be done as soon as possible. + +It is unlikely that we will put out a maintenance patch at the request of a given bid adapter or module owner. + +**2. What Pull Requests make it into a release?** + +Every PR that's merged into master will be part of a release. Here are the [PR review guidelines](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md). diff --git a/browsers.json b/browsers.json index 2dc6f1ddf90..cb523addc7e 100644 --- a/browsers.json +++ b/browsers.json @@ -1,9 +1,9 @@ { - "bs_ie_13_windows_10": { + "bs_ie_14_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "edge", - "browser_version": "13.0", + "browser_version": "14.0", "device": null, "os": "Windows" }, @@ -15,110 +15,38 @@ "device": null, "os": "Windows" }, - "bs_firefox_46_windows_10": { - "base": "BrowserStack", - "os_version": "10", - "browser": "firefox", - "browser_version": "46.0", - "device": null, - "os": "Windows" - }, - "bs_chrome_51_windows_10": { + "bs_chrome_62_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "51.0", + "browser_version": "62.0", "device": null, "os": "Windows" }, - "bs_ie_11_windows_8.1": { + "bs_chrome_61_windows_10": { "base": "BrowserStack", - "os_version": "8.1", - "browser": "ie", - "browser_version": "11.0", - "device": null, - "os": "Windows" - }, - "bs_firefox_46_windows_8.1": { - "base": "BrowserStack", - "os_version": "8.1", - "browser": "firefox", - "browser_version": "46.0", - "device": null, - "os": "Windows" - }, - "bs_chrome_51_windows_8.1": { - "base": "BrowserStack", - "os_version": "8.1", + "os_version": "10", "browser": "chrome", - "browser_version": "51.0", - "device": null, - "os": "Windows" - }, - "bs_ie_10_windows_8": { - "base": "BrowserStack", - "os_version": "8", - "browser": "ie", - "browser_version": "10.0", + "browser_version": "61.0", "device": null, "os": "Windows" }, - "bs_firefox_46_windows_8": { + "bs_firefox_58_windows_10": { "base": "BrowserStack", - "os_version": "8", + "os_version": "10", "browser": "firefox", - "browser_version": "46.0", - "device": null, - "os": "Windows" - }, - "bs_chrome_51_windows_8": { - "base": "BrowserStack", - "os_version": "8", - "browser": "chrome", - "browser_version": "51.0", + "browser_version": "58.0", "device": null, "os": "Windows" }, - "bs_ie_11_windows_7": { + "bs_firefox_57_windows_10": { "base": "BrowserStack", - "os_version": "7", - "browser": "ie", - "browser_version": "11.0", - "device": null, - "os": "Windows" - }, - "bs_ie_10_windows_7": { - "base": "BrowserStack", - "os_version": "7", - "browser": "ie", - "browser_version": "10.0", - "device": null, - "os": "Windows" - }, - "bs_firefox_46_windows_7": { - "base": "BrowserStack", - "os_version": "7", + "os_version": "10", "browser": "firefox", - "browser_version": "46.0", - "device": null, - "os": "Windows" - }, - "bs_chrome_51_windows_7": { - "base": "BrowserStack", - "os_version": "7", - "browser": "chrome", - "browser_version": "51.0", + "browser_version": "57.0", "device": null, "os": "Windows" }, - "bs_chrome_56_mac_sierra": { - "base": "BrowserStack", - "os": "OS X", - "os_version": "Sierra", - "browser": "chrome", - "device": null, - "browser_version": "56.0" - }, "bs_safari_9.1_mac_elcapitan": { "base": "BrowserStack", "os_version": "El Capitan", @@ -127,22 +55,6 @@ "device": null, "os": "OS X" }, - "bs_firefox_46_mac_elcapitan": { - "base": "BrowserStack", - "os_version": "El Capitan", - "browser": "firefox", - "browser_version": "46.0", - "device": null, - "os": "OS X" - }, - "bs_chrome_51_mac_elcapitan": { - "base": "BrowserStack", - "os_version": "El Capitan", - "browser": "chrome", - "browser_version": "51.0", - "device": null, - "os": "OS X" - }, "bs_safari_8_mac_yosemite": { "base": "BrowserStack", "os_version": "Yosemite", @@ -151,52 +63,12 @@ "device": null, "os": "OS X" }, - "bs_firefox_46_mac_yosemite": { - "base": "BrowserStack", - "os_version": "Yosemite", - "browser": "firefox", - "browser_version": "46.0", - "device": null, - "os": "OS X" - }, - "bs_chrome_51_mac_yosemite": { - "base": "BrowserStack", - "os_version": "Yosemite", - "browser": "chrome", - "browser_version": "51.0", - "device": null, - "os": "OS X" - }, - "bs_safari_7.1_mac_mavericks": { - "base": "BrowserStack", - "os_version": "Mavericks", - "browser": "safari", - "browser_version": "7.1", - "device": null, - "os": "OS X" - }, - "bs_firefox_46_mac_mavericks": { - "base": "BrowserStack", - "os_version": "Mavericks", - "browser": "firefox", - "browser_version": "46.0", - "device": null, - "os": "OS X" - }, - "bs_chrome_49_mac_mavericks": { - "base": "BrowserStack", - "os_version": "Mavericks", - "browser": "chrome", - "browser_version": "49.0", - "device": null, - "os": "OS X" - }, - "bs_ios_7": { + "bs_ios_9": { "base": "BrowserStack", "os": "ios", - "os_version": "7.0", + "os_version": "9.1", "browser": "iphone", - "device": "iPhone 5S", + "device": "iPhone 6S", "browser_version": null }, "bs_ios_8": { @@ -206,13 +78,5 @@ "browser": "iphone", "device": "iPhone 6", "browser_version": null - }, - "bs_ios_9": { - "base": "BrowserStack", - "os": "ios", - "os_version": "9.1", - "browser": "iphone", - "device": "iPhone 6S", - "browser_version": null } -} +} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index f80cfe91d34..d2955f7d777 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -28,6 +28,7 @@ var gulpif = require('gulp-if'); var sourcemaps = require('gulp-sourcemaps'); var through = require('through2'); var fs = require('fs'); +var jsEscape = require('gulp-js-escape'); var prebid = require('./package.json'); var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); @@ -70,12 +71,17 @@ function nodeBundle(modules) { }); } +// these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules +var explicitModules = [ + 'pre1api' +]; + function bundle(dev, moduleArr) { var modules = moduleArr || helpers.getArgModules(), allModules = helpers.getModuleNames(modules); if(modules.length === 0) { - modules = allModules; + modules = allModules.filter(module => !explicitModules.includes(module)); } else { var diff = _.difference(modules, allModules); if(diff.length !== 0) { @@ -118,7 +124,11 @@ function newKarmaCallback(done) { if (exitCode) { done(new Error('Karma tests failed with exit code ' + exitCode)); } else { - done(); + if (argv.browserstack) { + process.exit(0); + } else { + done(); + } } } } @@ -173,10 +183,11 @@ gulp.task('webpack', ['clean'], function () { // By default, this runs in headless chrome. // // If --watch is given, the task will re-run unit tests whenever the source code changes +// If --file "" is given, the task will only run tests in the specified file. // 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 gulp.task('test', ['clean'], function (done) { - var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch); + var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file); var browserOverride = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase); if (browserOverride.length > 0) { @@ -186,8 +197,9 @@ gulp.task('test', ['clean'], function (done) { new KarmaServer(karmaConf, newKarmaCallback(done)).start(); }); +// If --file "" is given, the task will only run tests in the specified file. gulp.task('test-coverage', ['clean'], function(done) { - new KarmaServer(karmaConfMaker(true, false), newKarmaCallback(done)).start(); + new KarmaServer(karmaConfMaker(true, false, false, argv.file), newKarmaCallback(done)).start(); }); // View the code coverage report in the browser. @@ -294,10 +306,21 @@ gulp.task('e2etest-report', function() { }, 5000); }); -gulp.task('build-postbid', function() { +// 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 +gulp.task('build-postbid', ['escape-postbid-config'], function() { + var fileContent = fs.readFileSync('./build/postbid/postbid-config.js', 'utf8'); + return gulp.src('./integrationExamples/postbid/oas/postbid.js') - .pipe(uglify()) - .pipe(gulp.dest('build/dist')); + .pipe(replace('\[%%postbid%%\]', fileContent)) + .pipe(gulp.dest('build/postbid/')); +}); + +// Dependant task for building postbid. It escapes postbid-config file. +gulp.task('escape-postbid-config', function() { + gulp.src('./integrationExamples/postbid/oas/postbid-config.js') + .pipe(jsEscape()) + .pipe(gulp.dest('build/postbid/')); }); module.exports = nodeBundle; diff --git a/integrationExamples/gpt/amp/README.md b/integrationExamples/gpt/amp/README.md index 48b08fe0de5..1d61fa1f256 100644 --- a/integrationExamples/gpt/amp/README.md +++ b/integrationExamples/gpt/amp/README.md @@ -1,3 +1,10 @@ +##WARNING +The below documented method of deploying prebid on AMP requires remote.html +This is being deprecated on March 29th. A new method the requires Prebid Server +is being developed, see [Prebid Server](http://github.com/prebid/prebid-server). + +## Old method: + This README provides steps to run amp example page. Add following entries to your hosts file diff --git a/integrationExamples/gpt/gdpr_hello_world.html b/integrationExamples/gpt/gdpr_hello_world.html new file mode 100644 index 00000000000..9f6194edb16 --- /dev/null +++ b/integrationExamples/gpt/gdpr_hello_world.html @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + \ No newline at end of file diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html index 0f5e24a301a..a5949b87c56 100644 --- a/integrationExamples/gpt/hello_world.html +++ b/integrationExamples/gpt/hello_world.html @@ -1,102 +1,98 @@ - - - - - - - - - - - - - - - -

Prebid.js Test

-
Div-1
-
- -
- - + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + \ No newline at end of file diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 77c875b9787..f1ec912fd26 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -116,7 +116,7 @@ }, { bidder: 'adform', - // available params: [ 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test' ] + // available params: [ 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test', priceType ] params: { adxDomain: 'adx.adform.net', //optional mid: 158989, @@ -268,6 +268,19 @@ params: { placement_id: 0 } + }, + { + bidder: 'pollux', + params: { + zone: '1806' // REQUIRED Zone Id (1806 is a test zone) + } + }, + { + bidder: 'adkernelAdn', + params: { + pubId: 50357, //REQUIRED + host: 'dsp-staging.adkernel.com' //OPTIONAL + } } ] }, { @@ -388,6 +401,18 @@ params: { placement_id: 0 } + }, + { + bidder: 'huddledmasses', + params: { + placement_id: 0 + } + }, + { + bidder: 'pollux', + params: { + zone: '276' // REQUIRED Zone Id (276 is a test zone) + } } ] } diff --git a/integrationExamples/gpt/pollux_example.html b/integrationExamples/gpt/pollux_example.html new file mode 100644 index 00000000000..56eedbf2a9c --- /dev/null +++ b/integrationExamples/gpt/pollux_example.html @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + test + + + +
+ +
+ +
+
+ +
+ +
+ + \ No newline at end of file diff --git a/integrationExamples/gpt/serverbidServer_example.html b/integrationExamples/gpt/serverbidServer_example.html new file mode 100644 index 00000000000..3d76e963663 --- /dev/null +++ b/integrationExamples/gpt/serverbidServer_example.html @@ -0,0 +1,103 @@ + + + + + + + + +

Prebid.js S2S Example

+ +
Div-1
+
+ +
+ + diff --git a/integrationExamples/gpt/pollux_zone_728x90.html b/integrationExamples/gpt/unruly_example.html similarity index 53% rename from integrationExamples/gpt/pollux_zone_728x90.html rename to integrationExamples/gpt/unruly_example.html index ecede9b5db2..77a9b02b3dd 100644 --- a/integrationExamples/gpt/pollux_zone_728x90.html +++ b/integrationExamples/gpt/unruly_example.html @@ -7,31 +7,38 @@ var PREBID_TIMEOUT = 3000; var adUnits = [{ - code: 'div-gpt-ad-1460505661639-0', - sizes: [[728, 90]], - bids: [ - { - bidder: 'pollux', - params: { - zone: '276' + code: 'ad-slot', + sizes: [[728, 90], [300, 250]], + mediaTypes: { + video: { + context: 'outstream' } }, - { - bidder: 'pollux', + bids: [{ + bidder: 'unruly', params: { - zone: '1806' + targetingUUID: '6f15e139-5f18-49a1-b52f-87e5e69ee65e', + siteId: 1081534 } } - ] + ] }]; - var pbjs = pbjs || {}; pbjs.que = pbjs.que || []; + + (function() { + var pbjsEl = document.createElement("script"); + pbjsEl.type = "text/javascript"; + pbjsEl.async = true; + pbjsEl.src = '/build/dev/prebid.js'; + var pbjsTargetEl = document.getElementsByTagName("head")[0]; + pbjsTargetEl.insertBefore(pbjsEl, pbjsTargetEl.firstChild); + })(); + - @@ -79,7 +94,7 @@ + - \ No newline at end of file + diff --git a/integrationExamples/postbid/oas/postbid-config.js b/integrationExamples/postbid/oas/postbid-config.js new file mode 100644 index 00000000000..f251938bc9c --- /dev/null +++ b/integrationExamples/postbid/oas/postbid-config.js @@ -0,0 +1,54 @@ + + \ No newline at end of file diff --git a/integrationExamples/postbid/oas/postbid.js b/integrationExamples/postbid/oas/postbid.js index a4aeb588b26..856f5f36207 100644 --- a/integrationExamples/postbid/oas/postbid.js +++ b/integrationExamples/postbid/oas/postbid.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function(window){ var postbid = {}; postbid.que = []; @@ -100,7 +101,22 @@ } postbid.que.push(function(conf) { - var content = "\n + + + +``` \ No newline at end of file diff --git a/modules/memeglobalBidAdapter.js b/modules/memeglobalBidAdapter.js deleted file mode 100644 index c47c8ed7381..00000000000 --- a/modules/memeglobalBidAdapter.js +++ /dev/null @@ -1,124 +0,0 @@ -var CONSTANTS = require('src/constants.json'); -var utils = require('src/utils.js'); -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader'); -var adaptermanager = require('src/adaptermanager'); - -var bidderName = 'memeglobal'; -/** - * Adapter for requesting bids from Meme Global Media Group - * OpenRTB compatible - */ -var MemeGlobalAdapter = function MemeGlobalAdapter() { - var bidder = 'stinger.memeglobal.com/api/v1/services/prebid'; - - function _callBids(params) { - var bids = params.bids; - - if (!bids) return; - - for (var i = 0; i < bids.length; i++) { - _requestBid(bids[i]); - } - } - - function _requestBid(bidReq) { - // build bid request object - var domain = window.location.host; - var page = window.location.host + window.location.pathname + location.search + location.hash; - - var tagId = utils.getBidIdParameter('tagid', bidReq.params); - var bidFloor = Number(utils.getBidIdParameter('bidfloor', bidReq.params)); - var adW = 0; - var adH = 0; - - var bidSizes = Array.isArray(bidReq.params.sizes) ? bidReq.params.sizes : bidReq.sizes; - var sizeArrayLength = bidSizes.length; - if (sizeArrayLength === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { - adW = bidSizes[0]; - adH = bidSizes[1]; - } else { - adW = bidSizes[0][0]; - adH = bidSizes[0][1]; - } - - // build bid request with impressions - var bidRequest = { - id: utils.getUniqueIdentifierStr(), - imp: [{ - id: bidReq.bidId, - banner: { - w: adW, - h: adH - }, - tagid: bidReq.placementCode, - bidfloor: bidFloor - }], - site: { - domain: domain, - page: page, - publisher: { - id: tagId - } - } - }; - - var scriptUrl = '//' + bidder + '?callback=window.$$PREBID_GLOBAL$$.mgres' + - '&src=' + CONSTANTS.REPO_AND_VERSION + - '&br=' + encodeURIComponent(JSON.stringify(bidRequest)); - adloader.loadScript(scriptUrl); - } - - function getBidSetForBidder() { - return $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === bidderName); - } - - // expose the callback to the global object: - $$PREBID_GLOBAL$$.mgres = function (bidResp) { - // valid object? - if ((!bidResp || !bidResp.id) || - (!bidResp.seatbid || bidResp.seatbid.length === 0 || !bidResp.seatbid[0].bid || bidResp.seatbid[0].bid.length === 0)) { - return; - } - - bidResp.seatbid[0].bid.forEach(function (bidderBid) { - var responseCPM; - var placementCode = ''; - - var bidSet = getBidSetForBidder(); - var bidRequested = bidSet.bids.find(b => b.bidId === bidderBid.impid); - if (bidRequested) { - var bidResponse = bidfactory.createBid(1); - placementCode = bidRequested.placementCode; - bidRequested.status = CONSTANTS.STATUS.GOOD; - responseCPM = parseFloat(bidderBid.price); - if (responseCPM === 0) { - var bid = bidfactory.createBid(2); - bid.bidderCode = bidderName; - bidmanager.addBidResponse(placementCode, bid); - return; - } - bidResponse.placementCode = placementCode; - bidResponse.size = bidRequested.sizes; - var responseAd = bidderBid.adm; - var responseNurl = ''; - bidResponse.creative_id = bidderBid.id; - bidResponse.bidderCode = bidderName; - bidResponse.cpm = responseCPM; - bidResponse.ad = decodeURIComponent(responseAd + responseNurl); - bidResponse.width = parseInt(bidderBid.w); - bidResponse.height = parseInt(bidderBid.h); - bidmanager.addBidResponse(placementCode, bidResponse); - } - }); - }; - - return { - callBids: _callBids - }; -}; - -adaptermanager.registerBidAdapter(new MemeGlobalAdapter(), 'memeglobal'); - -module.exports = MemeGlobalAdapter; diff --git a/modules/mobfoxBidAdapter.js b/modules/mobfoxBidAdapter.js index 39d2fe40f1d..3620d8d30e7 100644 --- a/modules/mobfoxBidAdapter.js +++ b/modules/mobfoxBidAdapter.js @@ -1,34 +1,25 @@ -const bidfactory = require('src/bidfactory.js'); -const bidmanager = require('src/bidmanager.js'); -const ajax = require('src/ajax.js'); -const CONSTANTS = require('src/constants.json'); +import {registerBidder} from 'src/adapters/bidderFactory'; + const utils = require('src/utils.js'); -const adaptermanager = require('src/adaptermanager'); - -function MobfoxAdapter() { - const BIDDER_CODE = 'mobfox'; - const BID_REQUEST_BASE_URL = 'https://my.mobfox.com/request.php'; - - // request - function buildQueryStringFromParams(params) { - for (let key in params) { - if (params.hasOwnProperty(key)) { - if (params[key] === undefined) { - delete params[key]; - } else { - params[key] = encodeURIComponent(params[key]); - } - } +const BIDDER_CODE = 'mobfox'; +const BID_REQUEST_BASE_URL = 'https://my.mobfox.com/request.php'; +const CPM_HEADER = 'X-Pricing-CPM'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['mf'], // short code + isBidRequestValid: function (bid) { + return bid.params.s !== null && bid.params.s !== undefined; + }, + buildRequests: function (validBidRequests) { + if (validBidRequests.length > 1) { + throw ('invalid number of valid bid requests, expected 1 element') } - return utils._map(Object.keys(params), key => `${key}=${params[key]}`) - .join('&'); - } - - function buildBidRequest(bid) { - let bidParams = bid.params; + let bidParams = validBidRequests[0].params; + let bid = validBidRequests[0]; - let requestParams = { + let params = { // -------------------- Mandatory Parameters ------------------ rt: bidParams.rt || 'api-fetchip', r_type: bidParams.r_type || 'banner', @@ -80,103 +71,63 @@ function MobfoxAdapter() { n_rating_req: bidParams.n_rating_req || undefined }; - return requestParams; - } + let payloadString = buildPayloadString(params); - function sendBidRequest(bid) { - let requestParams = buildBidRequest(bid); - let queryString = buildQueryStringFromParams(requestParams); - - ajax.ajax(`${BID_REQUEST_BASE_URL}?${queryString}`, { - success(resp, xhr) { - if (xhr.getResponseHeader('Content-Type') == 'application/json') { - try { - resp = JSON.parse(resp) - } catch (e) { - resp = {error: resp} - } - } - onBidResponse({ - data: resp, - xhr: xhr - }, bid); - }, - error(err) { - if (xhr.getResponseHeader('Content-Type') == 'application/json') { - try { - err = JSON.parse(err); - } catch (e) { - } - ; - } - onBidResponseError(bid, [err]); + return { + method: 'GET', + url: BID_REQUEST_BASE_URL, + data: payloadString, + requestId: bid.bidId + }; + }, + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + let serverResponseBody = serverResponse.body; + + if (!serverResponseBody || serverResponseBody.error) { + let errorMessage = `in response for ${BIDDER_CODE} adapter`; + if (serverResponseBody && serverResponseBody.error) { + errorMessage += `: ${serverResponseBody.error}`; } - }); - } - - // response - function onBidResponseError(bid, err) { - utils.logError('Bid Response Error', bid, ...err); - let bidResponse = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bid); - bidResponse.bidderCode = BIDDER_CODE; - bidmanager.addBidResponse(bid.placementCode, bidResponse); - } - - function onBidResponse(bidderResponse, bid) { - // transform the response to a valid prebid response - try { - let bidResponse = transformResponse(bidderResponse, bid); - bidmanager.addBidResponse(bid.placementCode, bidResponse); - } catch (e) { - onBidResponseError(bid, [e]); - } - } - - function transformResponse(bidderResponse, bid) { - let responseBody = bidderResponse.data; - - // Validate Request - let err = responseBody.error; - if (err) { - throw err; + utils.logError(errorMessage); + return bidResponses; } - - let htmlString = responseBody.request && responseBody.request.htmlString; - if (!htmlString) { - throw [`htmlString is missing`, responseBody]; - } - - let cpm; - const cpmHeader = bidderResponse.xhr.getResponseHeader('X-Pricing-CPM'); try { - cpm = Number(cpmHeader); + let serverResponseHeaders = serverResponse.headers; + let bidRequestData = bidRequest.data.split('&'); + const bidResponse = { + requestId: bidRequest.requestId, + cpm: serverResponseHeaders.get(CPM_HEADER), + width: bidRequestData[5].split('=')[1], + height: bidRequestData[6].split('=')[1], + creativeId: bidRequestData[3].split('=')[1], + currency: 'USD', + netRevenue: true, + ttl: 360, + referrer: serverResponseBody.request.clickurl, + ad: serverResponseBody.request.htmlString + }; + bidResponses.push(bidResponse); } catch (e) { - throw ['Invalid CPM value:', cpmHeader]; + throw 'could not build bid response: ' + e; } - - // Validations passed - Got bid - let bidResponse = bidfactory.createBid(CONSTANTS.STATUS.GOOD, bid); - bidResponse.bidderCode = BIDDER_CODE; - - bidResponse.ad = htmlString; - bidResponse.cpm = cpm; - - bidResponse.width = bid.sizes[0][0]; - bidResponse.height = bid.sizes[0][1]; - - return bidResponse; + return bidResponses; } - - // prebid api - function callBids(params) { - let bids = params.bids || []; - bids.forEach(sendBidRequest); +}; + +function buildPayloadString(params) { + for (let key in params) { + if (params.hasOwnProperty(key)) { + if (params[key] === undefined) { + delete params[key]; + } else { + params[key] = encodeURIComponent(params[key]); + } + } } - return { - callBids: callBids - }; + return utils._map(Object.keys(params), key => `${key}=${params[key]}`) + .join('&') } -adaptermanager.registerBidAdapter(new MobfoxAdapter(), 'mobfox'); -module.exports = MobfoxAdapter; +registerBidder(spec); diff --git a/modules/mobfoxBidAdapter.md b/modules/mobfoxBidAdapter.md new file mode 100644 index 00000000000..31b60606d2f --- /dev/null +++ b/modules/mobfoxBidAdapter.md @@ -0,0 +1,29 @@ +# Overview + +``` +Module Name: Mobfox Bidder Adapter +Module Type: Bidder Adapter +Maintainer: solutions-team@matomy.com +``` + +# Description + +Module that connects to Mobfox's demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + sizes: [[320, 480], [300, 250], [300,600]], + + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'mobfox', + params: { + s: "267d72ac3f77a3f447b32cf7ebf20673", // required - The hash of your inventory to identify which app is making the request, + imp_instl: 1 // optional - set to 1 if using interstitial otherwise delete or set to 0 + } + }] + + }]; +``` diff --git a/modules/nanointeractiveBidAdapter.js b/modules/nanointeractiveBidAdapter.js new file mode 100644 index 00000000000..549695369c8 --- /dev/null +++ b/modules/nanointeractiveBidAdapter.js @@ -0,0 +1,84 @@ +import * as utils from 'src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes'; + +export const BIDDER_CODE = 'nanointeractive'; +export const ENGINE_BASE_URL = 'https://www.audiencemanager.de/hb'; + +export const DATA_PARTNER_PIXEL_ID = 'pid'; +export const NQ = 'nq'; +export const NQ_NAME = 'name'; +export const CATEGORY = 'category'; +export const SUB_ID = 'subId'; + +export const spec = { + + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid(bid) { + const pid = bid.params[DATA_PARTNER_PIXEL_ID]; + return !!(pid); + }, + buildRequests(bidRequests) { + let payload = []; + bidRequests.forEach(bid => payload.push(createSingleBidRequest(bid))); + return { + method: 'POST', + url: ENGINE_BASE_URL, + data: JSON.stringify(payload) + }; + }, + interpretResponse(serverResponse) { + const bids = []; + serverResponse.body.forEach(serverBid => { + if (isEngineResponseValid(serverBid)) { + bids.push(createSingleBidResponse(serverBid)); + } + }); + return bids; + } +}; + +function createSingleBidRequest(bid) { + return { + [DATA_PARTNER_PIXEL_ID]: bid.params[DATA_PARTNER_PIXEL_ID], + [NQ]: [createNqParam(bid), createCategoryParam(bid)], + [SUB_ID]: createSubIdParam(bid), + sizes: bid.sizes.map(value => value[0] + 'x' + value[1]), + bidId: bid.bidId, + cors: utils.getOrigin() + }; +} + +function createSingleBidResponse(serverBid) { + return { + requestId: serverBid.id, + cpm: serverBid.cpm, + width: serverBid.width, + height: serverBid.height, + ad: serverBid.ad, + ttl: serverBid.ttl, + creativeId: serverBid.creativeId, + netRevenue: serverBid.netRevenue || true, + currency: serverBid.currency, + }; +} + +function createNqParam(bid) { + return bid.params[NQ_NAME] ? utils.getParameterByName(bid.params[NQ_NAME]) : bid.params[NQ] || null; +} + +function createCategoryParam(bid) { + return bid.params[CATEGORY] || null; +} + +function createSubIdParam(bid) { + return bid.params[SUB_ID] || null; +} + +function isEngineResponseValid(response) { + return !!response.cpm && !!response.ad; +} + +registerBidder(spec); diff --git a/modules/nanointeractiveBidAdapter.md b/modules/nanointeractiveBidAdapter.md new file mode 100644 index 00000000000..0813a461493 --- /dev/null +++ b/modules/nanointeractiveBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: NanoInteractive Bid Adapter +Module Type: Bidder Adapter +Maintainer: rade@nanointeractive.com +``` + +# Description + +Connects to Nano Interactive search retargeting Ad Server for bids. + +Besides standard params, please provide, if exist, user search params. + +Three examples calling the Ad Server. + +**First** is basic + +**Second** is with hardcoded nq (user search) params + +**Third** is with the search query param name of the current url + +# Test Parameters +``` +var adUnits = [ + // Basic call + { + code: 'basic-div', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'nanointeractive', + params: { + // required + pid: '58bfec94eb0a1916fa380163', + // optional parameters + category: 'some category', + subId: '123' + } + }] + }, + // Hardcoded user search + { + code: 'nq-div', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'nanointeractive', + params: { + // required + pid: '58bfec94eb0a1916fa380163', + // optional parameters + nq: 'user search', + category: 'some category', + subId: '123' + } + }] + }, + // URL user search + { + code: 'url-div', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'nanointeractive', + params: { + // required + pid: '58bfec94eb0a1916fa380163', + // optional parameters + name: 'search', + category: 'some category', + subId: '123' + } + }] + } +]; +``` + +### Requirements: +To be able to get identification key (`pid`), you must register at
+`https://audiencemanager.de/public/data-partners-register`
+and follow further instructions. \ No newline at end of file diff --git a/modules/nasmediaAdmixerBidAdapter.js b/modules/nasmediaAdmixerBidAdapter.js new file mode 100644 index 00000000000..7de5c638c9e --- /dev/null +++ b/modules/nasmediaAdmixerBidAdapter.js @@ -0,0 +1,82 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import find from 'core-js/library/fn/array/find'; + +const ADMIXER_ENDPOINT = 'https://adn.admixer.co.kr:10443/prebid'; +const DEFAULT_BID_TTL = 360; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_REVENUE = false; + +export const spec = { + code: 'nasmediaAdmixer', + + isBidRequestValid: function (bid) { + return !!(bid && bid.params && bid.params.ax_key); + }, + + buildRequests: function (validBidRequests) { + return validBidRequests.map(bid => { + let adSize = getSize(bid.sizes); + + return { + method: 'GET', + url: ADMIXER_ENDPOINT, + data: { + ax_key: utils.getBidIdParameter('ax_key', bid.params), + req_id: bid.bidId, + width: adSize.width, + height: adSize.height, + referrer: utils.getTopWindowUrl(), + os: getOsType() + } + } + }) + }, + + interpretResponse: function (serverResponse, bidRequest) { + const serverBody = serverResponse.body; + const bidResponses = []; + + if (serverBody && serverBody.error_code === 0 && serverBody.body && serverBody.body.length > 0) { + let bidData = serverBody.body[0]; + + const bidResponse = { + ad: bidData.ad, + requestId: serverBody.req_id, + creativeId: bidData.ad_id, + cpm: bidData.cpm, + width: bidData.width, + height: bidData.height, + currency: bidData.currency ? bidData.currency : DEFAULT_CURRENCY, + netRevenue: DEFAULT_REVENUE, + ttl: DEFAULT_BID_TTL + }; + + bidResponses.push(bidResponse); + } + return bidResponses; + } +} + +function getOsType() { + let ua = navigator.userAgent.toLowerCase(); + let os = ['android', 'ios', 'mac', 'linux', 'window']; + let regexp_os = [/android/i, /iphone|ipad/i, /mac/i, /linux/i, /window/i]; + + return find(os, (tos, idx) => { + if (ua.match(regexp_os[idx])) { + return os[idx]; + } + }) || 'etc'; +} + +function getSize(sizes) { + let parsedSizes = utils.parseSizesInput(sizes); + let [width, height] = parsedSizes.length ? parsedSizes[0].split('x') : []; + + return { + width: parseInt(width, 10), + height: parseInt(height, 10) + }; +} +registerBidder(spec); diff --git a/modules/nasmediaAdmixerBidAdapter.md b/modules/nasmediaAdmixerBidAdapter.md new file mode 100644 index 00000000000..44797e413a8 --- /dev/null +++ b/modules/nasmediaAdmixerBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: NasmediaAdmixer Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@nasmedia.co.kr +``` + +# Description + +Module that connects to NasmediaAdmixer demand sources. +Banner formats are supported. +The NasmediaAdmixer adapter doesn't support multiple sizes per ad-unit and will use the first one if multiple sizes are defined. + + +# Test Parameters +``` + var adUnits = [ + { + code: 'banner-ad-div', + sizes: [[320, 480]], // banner size + bids: [ + { + bidder: 'nasmediaAdmixer', + params: { + ax_key: 'ajj7jba3', //required parameter + } + } + ] + } + ]; +``` diff --git a/modules/nginadBidAdapter.js b/modules/nginadBidAdapter.js deleted file mode 100644 index 32560120d64..00000000000 --- a/modules/nginadBidAdapter.js +++ /dev/null @@ -1,186 +0,0 @@ -var CONSTANTS = require('src/constants.json'); -var utils = require('src/utils.js'); -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader'); -var adaptermanager = require('src/adaptermanager'); - -var defaultPlacementForBadBid = null; - -/** - * Adapter for requesting bids from NginAd - */ -var NginAdAdapter = function NginAdAdapter() { - var rtbServerDomain = 'placeholder.for.nginad.server.com'; - - function _callBids(params) { - var nginadBids = params.bids || []; - - // De-dupe by tagid then issue single bid request for all bids - _requestBids(_getUniqueTagids(nginadBids)); - } - - // filter bids to de-dupe them? - function _getUniqueTagids(bids) { - var key; - var map = {}; - var PubZoneIds = []; - - for (key in bids) { - map[utils.getBidIdParameter('pzoneid', bids[key].params)] = bids[key]; - } - - for (key in map) { - if (map.hasOwnProperty(key)) { - PubZoneIds.push(map[key]); - } - } - - return PubZoneIds; - } - - function getWidthAndHeight(bid) { - var adW = null; - var adH = null; - - var sizeArrayLength = bid.sizes.length; - if (sizeArrayLength === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { - adW = bid.sizes[0]; - adH = bid.sizes[1]; - } else { - adW = bid.sizes[0][0]; - adH = bid.sizes[0][1]; - } - - return [adW, adH]; - } - - function _requestBids(bidReqs) { - // build bid request object - var domain = window.location.host; - var page = window.location.pathname + location.search + location.hash; - - var nginadImps = []; - - // assign the first adUnit (placement) for bad bids; - defaultPlacementForBadBid = bidReqs[0].placementCode; - - // build impression array for nginad - utils._each(bidReqs, function(bid) { - var tagId = utils.getBidIdParameter('pzoneid', bid.params); - var bidFloor = utils.getBidIdParameter('bidfloor', bid.params); - - var whArr = getWidthAndHeight(bid); - - var imp = { - id: bid.bidId, - banner: { - w: whArr[0], - h: whArr[1] - }, - tagid: tagId, - bidfloor: bidFloor - }; - - nginadImps.push(imp); - // bidmanager.pbCallbackMap[imp.id] = bid; - - rtbServerDomain = bid.params.nginadDomain; - }); - - // build bid request with impressions - var nginadBidReq = { - id: utils.getUniqueIdentifierStr(), - imp: nginadImps, - site: { - domain: domain, - page: page - } - }; - - var scriptUrl = window.location.protocol + '//' + rtbServerDomain + '/bid/rtb?callback=window.$$PREBID_GLOBAL$$.nginadResponse' + - '&br=' + encodeURIComponent(JSON.stringify(nginadBidReq)); - - adloader.loadScript(scriptUrl); - } - - function handleErrorResponse(bidReqs, defaultPlacementForBadBid) { - // no response data - if (defaultPlacementForBadBid === null) { - // no id with which to create an dummy bid - return; - } - - var bid = bidfactory.createBid(2); - bid.bidderCode = 'nginad'; - bidmanager.addBidResponse(defaultPlacementForBadBid, bid); - } - - // expose the callback to the global object: - $$PREBID_GLOBAL$$.nginadResponse = function(nginadResponseObj) { - var bid = {}; - var key; - - // valid object? - if (!nginadResponseObj || !nginadResponseObj.id) { - return handleErrorResponse(nginadResponseObj, defaultPlacementForBadBid); - } - - if (!nginadResponseObj.seatbid || nginadResponseObj.seatbid.length === 0 || !nginadResponseObj.seatbid[0].bid || nginadResponseObj.seatbid[0].bid.length === 0) { - return handleErrorResponse(nginadResponseObj, defaultPlacementForBadBid); - } - - for (key in nginadResponseObj.seatbid[0].bid) { - var nginadBid = nginadResponseObj.seatbid[0].bid[key]; - - var responseCPM; - var placementCode = ''; - var id = nginadBid.impid; - - // try to fetch the bid request we sent NginAd - var bidObj = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'nginad').bids - .find(bid => bid.bidId === id); - if (!bidObj) { - return handleErrorResponse(nginadBid, defaultPlacementForBadBid); - } - - placementCode = bidObj.placementCode; - bidObj.status = CONSTANTS.STATUS.GOOD; - - // place ad response on bidmanager._adResponsesByBidderId - responseCPM = parseFloat(nginadBid.price); - - if (responseCPM === 0) { - handleErrorResponse(nginadBid, id); - } - - nginadBid.placementCode = placementCode; - nginadBid.size = bidObj.sizes; - var responseAd = nginadBid.adm; - - // store bid response - // bid status is good (indicating 1) - bid = bidfactory.createBid(1); - bid.creative_id = nginadBid.Id; - bid.bidderCode = 'nginad'; - bid.cpm = responseCPM; - - // The bid is a mock bid, the true bidding process happens after the publisher tag is called - bid.ad = decodeURIComponent(responseAd); - - var whArr = getWidthAndHeight(bidObj); - bid.width = whArr[0]; - bid.height = whArr[1]; - - bidmanager.addBidResponse(placementCode, bid); - } - }; // nginadResponse - - return { - callBids: _callBids - }; -}; - -adaptermanager.registerBidAdapter(new NginAdAdapter(), 'nginad'); - -module.exports = NginAdAdapter; diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js new file mode 100644 index 00000000000..c56669b8b0c --- /dev/null +++ b/modules/oneVideoBidAdapter.js @@ -0,0 +1,189 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'oneVideo'; +export const spec = { + code: 'oneVideo', + ENDPOINT: '//ads.adaptv.advertising.com/rtb/openrtb?ext_id=', + SYNC_ENDPOINT1: 'https://cm.g.doubleclick.net/pixel?google_nid=adaptv_dbm&google_cm&google_sc', + SYNC_ENDPOINT2: 'https://pr-bh.ybp.yahoo.com/sync/adaptv_ortb/{combo_uid}', + SYNC_ENDPOINT3: 'https://sync-tm.everesttech.net/upi/pid/m7y5t93k?redir=https%3A%2F%2Fsync.adap.tv%2Fsync%3Ftype%3Dgif%26key%3Dtubemogul%26uid%3D%24%7BUSER_ID%7D', + SYNC_ENDPOINT4: 'https://match.adsrvr.org/track/cmf/generic?ttd_pid=adaptv&ttd_tpi=1', + supportedMediaTypes: ['video'], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + + // Video validations + if (typeof bid.params.video === 'undefined' || typeof bid.params.video.playerWidth === 'undefined' || typeof bid.params.video.playerHeight == 'undefined' || typeof bid.params.video.mimes == 'undefined') { + return false; + } + + // Pub Id validation + if (typeof bid.params.pubId === 'undefined') { + 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(bids) { + return bids.map(bid => { + return { + method: 'POST', + url: location.protocol + spec.ENDPOINT + bid.params.pubId, + data: getRequestData(bid), + options: {contentType: 'application/json'}, + bidRequest: bid + } + }) + }, + /** + * 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(response, { bidRequest }) { + let bid; + let size; + let bidResponse; + try { + response = response.body; + bid = response.seatbid[0].bid[0]; + } catch (e) { + response = null; + } + if (!response || !bid || (!bid.adm && !bid.nurl) || !bid.price) { + utils.logWarn(`No valid bids from ${spec.code} bidder`); + return []; + } + size = getSize(bidRequest.sizes); + bidResponse = { + requestId: bidRequest.bidId, + bidderCode: spec.code, + cpm: bid.price, + creativeId: bid.id, + width: size.width, + height: size.height, + mediaType: 'video', + currency: response.cur, + ttl: 100, + netRevenue: true + }; + if (bid.nurl) { + bidResponse.vastUrl = bid.nurl; + } else if (bid.adm) { + bidResponse.vastXml = bid.adm; + } + return bidResponse; + }, + /** + * 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) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: spec.SYNC_ENDPOINT1 + }, + { + type: 'image', + url: spec.SYNC_ENDPOINT2 + }, + { + type: 'image', + url: spec.SYNC_ENDPOINT3 + }, + { + type: 'image', + url: spec.SYNC_ENDPOINT4 + }]; + } + } +}; + +function getSize(sizes) { + let parsedSizes = utils.parseSizesInput(sizes); + let [ width, height ] = parsedSizes.length ? parsedSizes[0].split('x') : []; + return { + width: parseInt(width, 10) || undefined, + height: parseInt(height, 10) || undefined + }; +} + +function getRequestData(bid) { + let loc = utils.getTopWindowLocation(); + let global = (window.top) ? window.top : window; + let page = (bid.params.site.page) ? (bid.params.site.page) : (loc.href); + let ref = (bid.params.site.referrer) ? bid.params.site.referrer : utils.getTopWindowReferrer(); + let bidData = { + id: utils.generateUUID(), + at: 2, + cur: bid.cur || 'USD', + imp: [{ + id: '1', + secure: isSecure(), + bidfloor: bid.params.bidfloor, + video: { + mimes: bid.params.video.mimes, + w: bid.params.video.playerWidth, + h: bid.params.video.playerHeight, + linearity: 1, + protocols: bid.params.video.protocols || [2, 5] + } + }], + site: { + page: page, + ref: ref + }, + device: { + ua: global.navigator.userAgent + }, + tmax: 200 + }; + + if (bid.params.video.maxbitrate) { + bidData.imp[0].video.maxbitrate = bid.params.video.maxbitrate + } + if (bid.params.video.maxduration) { + bidData.imp[0].video.maxduration = bid.params.video.maxduration + } + if (bid.params.video.minduration) { + bidData.imp[0].video.minduration = bid.params.video.minduration + } + if (bid.params.video.api) { + bidData.imp[0].video.api = bid.params.video.api + } + if (bid.params.video.delivery) { + bidData.imp[0].video.delivery = bid.params.video.delivery + } + if (bid.params.video.position) { + bidData.imp[0].video.pos = bid.params.video.position + } + if (bid.params.site.id) { + bidData.site.id = bid.params.site.id + } + return bidData; +} + +function isSecure() { + return document.location.protocol === 'https:'; +} + +registerBidder(spec); diff --git a/modules/oneVideoBidAdapter.md b/modules/oneVideoBidAdapter.md new file mode 100755 index 00000000000..96399221315 --- /dev/null +++ b/modules/oneVideoBidAdapter.md @@ -0,0 +1,47 @@ +# Overview + +**Module Name**: One Video Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: ankur.modi@oath.com + +# Description + +Connects to One Video demand source to fetch bids. + + +# Test Parameters +``` + var adUnits = [ + { + code: 'video1', + sizes: [640,480], + mediaTypes: { + video: { + context: "instream" + } + }, + bids: [ + { + bidder: 'oneVideo', + params: { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2] + }, + site: { + id: 1, + page: 'http://abhi12345.com', + referrer: 'http://abhi12345.com' + }, + pubId: 'brxd' + } + } + ] + } + ]; +``` diff --git a/modules/oneplanetonlyBidAdapter.js b/modules/oneplanetonlyBidAdapter.js new file mode 100644 index 00000000000..a6a3257a28b --- /dev/null +++ b/modules/oneplanetonlyBidAdapter.js @@ -0,0 +1,76 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; + +const BIDDER_CODE = 'oneplanetonly'; +const EDNPOINT = '//show.oneplanetonly.com/prebid'; + +function createEndpoint(siteId) { + return `${EDNPOINT}?siteId=${siteId}`; +} + +function isBidRequestValid (bid) { + return !!(bid.params.siteId && bid.params.adUnitId); +} + +function buildRequests(bidReqs) { + let firstBid = bidReqs[0] || {} + let siteId = utils.getBidIdParameter('siteId', firstBid.params) + let adUnits = bidReqs.map((bid) => { + return { + id: utils.getBidIdParameter('adUnitId', bid.params), + bidId: bid.bidId, + sizes: utils.parseSizesInput(bid.sizes), + }; + }); + + const bidRequest = { + id: firstBid.auctionId, + ver: 1, + prebidVer: `$prebid.version$`, + transactionId: firstBid.transactionId, + currency: config.getConfig('currency.adServerCurrency'), + timeout: config.getConfig('bidderTimeout'), + siteId, + domain: utils.getTopWindowLocation().hostname, + page: config.getConfig('pageUrl') || utils.getTopWindowUrl(), + referrer: utils.getTopWindowReferrer(), + adUnits, + }; + + return { + method: 'POST', + url: createEndpoint(siteId), + data: bidRequest, + options: {contentType: 'application/json', withCredentials: true} + }; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse.body.bids) { + return []; + } + return serverResponse.body.bids.map((bid) => { + return { + requestId: bid.requestId, + cpm: bid.cpm, + width: bid.width, + height: bid.height, + creativeId: bid.creativeId, + currency: bid.currency, + netRevenue: true, + ad: bid.ad, + ttl: bid.ttl + }; + }); +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['opo'], // short code + isBidRequestValid, + buildRequests, + interpretResponse +} + +registerBidder(spec); diff --git a/modules/oneplanetonlyBidAdapter.md b/modules/oneplanetonlyBidAdapter.md new file mode 100644 index 00000000000..973adb33efd --- /dev/null +++ b/modules/oneplanetonlyBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +``` +Module Name: OnePlanetOnly Bidder Adapter +Module Type: Bidder Adapter +Maintainer: vitaly@oneplanetonly.com +``` + +# Description + +Module that connects to OnePlanetOnly's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'desktop-banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bids: [ + { + bidder: 'oneplanetonly', + params: { + siteId: '5', + adUnitId: '5-4587544' + } + } + ] + },{ + code: 'mobile-banner-ad-div', + mediaTypes: { + banner: { + sizes: [[320, 50], [320, 100]], + } + }, + bids: [ + { + bidder: "oneplanetonly", + params: { + siteId: '5', + adUnitId: '5-81037880' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js new file mode 100644 index 00000000000..96a85a6070d --- /dev/null +++ b/modules/onetagBidAdapter.js @@ -0,0 +1,170 @@ +'use strict'; + +const { registerBidder } = require('../src/adapters/bidderFactory'); + +const ENDPOINT = 'https://onetag-sys.com/prebid-request'; +const BIDDER_CODE = 'onetag'; +const BANNER = 'banner'; + +// ======= +// Object BidRequest +// +// .params +// required .pubId: string +// optional .type: "BANNER" | "VIDEO" | "NATIVE" --> only BANNER at present + +/** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ +function isBidRequestValid(bid) { + if (typeof bid === 'undefined' || bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + + if (typeof bid.params.pubId !== 'string' || bid.sizes === 'undefined' || bid.sizes.length === 0) { + 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. + */ + +function buildRequests(validBidRequests) { + const bids = validBidRequests.map(requestsToBids); + const bidObject = {'bids': bids}; + const pageInfo = getPageInfo(); + + const payload = Object.assign(bidObject, pageInfo); + const payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: ENDPOINT, + data: payloadString + } +} + +function interpretResponse(serverResponse, request) { + var body = serverResponse.body; + const bids = []; + + if (typeof serverResponse === 'string') { + try { + body = JSON.parse(serverResponse); + } catch (e) { + return bids; + } + } + + if (!body || (body.nobid && body.nobid === true)) { + return bids; + } + + if (body.bids) { + body.bids.forEach(function(bid) { + bids.push({ + requestId: bid.requestId, + cpm: bid.cpm, + width: bid.width, + height: bid.height, + creativeId: bid.creativeId, + dealId: bid.dealId ? bid.dealId : '', + currency: bid.currency, + netRevenue: false, + mediaType: bids.type ? bids.type : BANNER, + ad: bid.ad, + ttl: bid.ttl || 6000 + }); + }); + } + + return bids; +} + +/** + * Returns information about the page needed by the server in an object to be converted in JSON + * @returns {{location: *, referrer: (*|string), masked: *, wWidth: (*|Number), wHeight: (*|Number), sWidth, sHeight, date: string, timeOffset: number}} + */ +function getPageInfo() { + var w, d, l, r, m, p, e, t, s; + for (w = window, d = w.document, l = d.location.href, r = d.referrer, m = 0, e = encodeURIComponent, t = new Date(), s = screen; w !== w.parent;) { + try { + p = w.parent; l = p.location.href; r = p.document.referrer; w = p; + } catch (e) { + m = top !== w.parent ? 2 : 1; + break + } + } + + const params = { + + location: e(l), + referrer: e(r) || '0', + masked: m, + wWidth: w.innerWidth, + wHeight: w.innerHeight, + sWidth: s.width, + sHeight: s.height, + date: t.toUTCString(), + timeOffset: t.getTimezoneOffset() + }; + + return params; +} + +function requestsToBids(bid) { + const toRet = {}; + + const params = bid.params; + + toRet['adUnitCode'] = bid.adUnitCode; + toRet['bidId'] = bid.bidId; + toRet['bidderRequestId'] = bid.bidderRequestId; + toRet['auctionId'] = bid.auctionId; + toRet['transactionId'] = bid.transactionId; + toRet['sizes'] = []; + const sizes = bid.sizes; + for (let i = 0, lenght = sizes.length; i < lenght; i++) { + const size = sizes[i]; + toRet['sizes'].push({width: size[0], height: size[1]}) + } + + toRet['pubId'] = params.pubId; + if (params.type) { + toRet['type'] = params.type; + } + + if (params.pubClick) { + toRet['click'] = params.pubClick; + } + + if (params.dealId) { + toRet['dealId'] = params.dealId; + } + + return toRet; +} + +// Va bene così, questo file va aggiunto a prebidmaster +export const spec = { + + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: isBidRequestValid, + buildRequests: buildRequests, + interpretResponse: interpretResponse, + +}; + +// Starting point +registerBidder(spec); diff --git a/modules/onetagBidAdapter.md b/modules/onetagBidAdapter.md new file mode 100644 index 00000000000..38872ad8280 --- /dev/null +++ b/modules/onetagBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: OneTag Bid Adapter +Module Type: Bidder Adapter +Maintainer: devops@onetag.com +``` + +# Description + +OneTag Bid Adapter supports only banner at present. + +# Test Parameters +``` + var adUnits = [ + { + code: "test-div", + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: "onetag", + params: { + pubId: "your_publisher_id", // required + type: "banner" // optional. Default "banner" + }, + } + ] + }]; + +``` diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 1b9766553c2..2f116323a42 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -1,298 +1,395 @@ -import { config } from 'src/config'; -const bidfactory = require('src/bidfactory.js'); -const bidmanager = require('src/bidmanager.js'); -const ajax = require('src/ajax'); -const CONSTANTS = require('src/constants.json'); -const utils = require('src/utils.js'); -const adaptermanager = require('src/adaptermanager'); - -const OpenxAdapter = function OpenxAdapter() { - const BIDDER_CODE = 'openx'; - const BIDDER_CONFIG = 'hb_pb'; - const BIDDER_VERSION = '1.0.1'; - let startTime; - let timeout = config.getConfig('bidderTimeout'); - let shouldSendBoPixel = true; - - let pdNode = null; - - function oxARJResponse (oxResponseObj) { - try { - oxResponseObj = JSON.parse(oxResponseObj); - } catch (_) { - // Could not parse response, changing to an empty response instead - oxResponseObj = { - ads: {} - }; +import {config} from 'src/config'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; +import {userSync} from 'src/userSync'; +import {BANNER, VIDEO} from 'src/mediaTypes'; +import {parse} from 'src/url'; + +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const BIDDER_CODE = 'openx'; +const BIDDER_CONFIG = 'hb_pb'; +const BIDDER_VERSION = '2.1.0'; + +let shouldSendBoPixel = true; +export function resetBoPixel() { + shouldSendBoPixel = true; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function (bidRequest) { + return !!(bidRequest.params.unit && bidRequest.params.delDomain); + }, + buildRequests: function (bidRequests, bidderRequest) { + if (bidRequests.length === 0) { + return []; } - let adUnits = oxResponseObj.ads.ad; - if (oxResponseObj.ads && oxResponseObj.ads.pixels) { - makePDCall(oxResponseObj.ads.pixels); + + let requests = []; + let [videoBids, bannerBids] = partitionByVideoBids(bidRequests); + + // build banner requests + if (bannerBids.length > 0) { + requests.push(buildOXBannerRequest(bannerBids, bidderRequest)); + } + // build video requests + if (videoBids.length > 0) { + videoBids.forEach(videoBid => { + requests.push(buildOXVideoRequest(videoBid, bidderRequest)) + }); } - if (!adUnits) { - adUnits = []; + return requests; + }, + interpretResponse: function ({body: oxResponseObj}, serverRequest) { + let mediaType = getMediaTypeFromRequest(serverRequest); + + return mediaType === VIDEO ? createVideoBidResponses(oxResponseObj, serverRequest.payload) + : createBannerBidResponses(oxResponseObj, serverRequest.payload); + }, + getUserSyncs: function(syncOptions, responses) { + if (syncOptions.iframeEnabled) { + let url = utils.deepAccess(responses, '0.body.ads.pixels') || + utils.deepAccess(responses, '0.body.pixels') || + '//u.openx.net/w/1.0/pd'; + return [{ + type: 'iframe', + url: url, + }]; } + } +}; - let bids = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'openx').bids; - for (let i = 0; i < bids.length; i++) { - let bid = bids[i]; - let auid = null; - let adUnit = null; - // find the adunit in the response - for (let j = 0; j < adUnits.length; j++) { - adUnit = adUnits[j]; - if (String(bid.params.unit) === String(adUnit.adunitid) && adUnitHasValidSizeFromBid(adUnit, bid) && !adUnit.used) { - auid = adUnit.adunitid; - break; - } - } +function isVideoRequest(bidRequest) { + return utils.deepAccess(bidRequest, 'mediaTypes.video') || bidRequest.mediaType === VIDEO; +} - let beaconParams = { - bd: +(new Date()) - startTime, - br: '0', // may be 0, t, or p - bt: Math.min(timeout, window.PREBID_TIMEOUT || config.getConfig('bidderTimeout')), - bs: window.location.hostname - }; - // no fill :( - if (!auid || !adUnit.pub_rev) { - addBidResponse(null, bid); - continue; - } - adUnit.used = true; - - beaconParams.br = beaconParams.bt < beaconParams.bd ? 't' : 'p'; - beaconParams.bp = adUnit.pub_rev; - beaconParams.ts = adUnit.ts; - addBidResponse(adUnit, bid); - if (shouldSendBoPixel === true) { - buildBoPixel(adUnit.creative[0], beaconParams); - } - } - }; +function createBannerBidResponses(oxResponseObj, {bids, startTime}) { + let adUnits = oxResponseObj.ads.ad; + let bidResponses = []; + for (let i = 0; i < adUnits.length; i++) { + let adUnit = adUnits[i]; + let adUnitIdx = parseInt(adUnit.idx, 10); + let bidResponse = {}; - function getViewportDimensions(isIfr) { - let width; - let height; - let tWin = window; - let tDoc = document; - let docEl = tDoc.documentElement; - let body; - - if (isIfr) { - try { - tWin = window.top; - tDoc = window.top.document; - } catch (e) { - return; - } - docEl = tDoc.documentElement; - body = tDoc.body; + bidResponse.requestId = bids[adUnitIdx].bidId; - width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; - height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; + if (adUnit.pub_rev) { + bidResponse.cpm = Number(adUnit.pub_rev) / 1000; } else { - docEl = tDoc.documentElement; - width = tWin.innerWidth || docEl.clientWidth; - height = tWin.innerHeight || docEl.clientHeight; + // No fill, do not add the bidresponse + continue; } + let creative = adUnit.creative[0]; + if (creative) { + bidResponse.width = creative.width; + bidResponse.height = creative.height; + } + bidResponse.creativeId = creative.id; + bidResponse.ad = adUnit.html; + if (adUnit.deal_id) { + bidResponse.dealId = adUnit.deal_id; + } + // default 5 mins + bidResponse.ttl = 300; + // true is net, false is gross + bidResponse.netRevenue = true; + bidResponse.currency = adUnit.currency; + + // additional fields to add + if (adUnit.tbd) { + bidResponse.tbd = adUnit.tbd; + } + bidResponse.ts = adUnit.ts; - return `${width}x${height}`; - } - - function makePDCall(pixelsUrl) { - let pdFrame = utils.createInvisibleIframe(); - let name = 'openx-pd'; - pdFrame.setAttribute('id', name); - pdFrame.setAttribute('name', name); - let rootNode = document.body; + bidResponses.push(bidResponse); - if (!rootNode) { + registerBeacon(BANNER, adUnit, startTime); + } + return bidResponses; +} + +function buildQueryStringFromParams(params) { + for (let key in params) { + if (params.hasOwnProperty(key)) { + if (!params[key]) { + delete params[key]; + } + } + } + return utils._map(Object.keys(params), key => `${key}=${params[key]}`) + .join('&'); +} + +function getViewportDimensions(isIfr) { + let width; + let height; + let tWin = window; + let tDoc = document; + let docEl = tDoc.documentElement; + let body; + + if (isIfr) { + try { + tWin = window.top; + tDoc = window.top.document; + } catch (e) { return; } + docEl = tDoc.documentElement; + body = tDoc.body; + + width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; + height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; + } else { + docEl = tDoc.documentElement; + width = tWin.innerWidth || docEl.clientWidth; + height = tWin.innerHeight || docEl.clientHeight; + } - pdFrame.src = pixelsUrl; + return `${width}x${height}`; +} - if (pdNode) { - pdNode.parentNode.replaceChild(pdFrame, pdNode); - pdNode = pdFrame; +function formatCustomParms(customKey, customParams) { + let value = customParams[customKey]; + if (utils.isArray(value)) { + // if value is an array, join them with commas first + value = value.join(','); + } + // return customKey=customValue format, escaping + to . and / to _ + return (customKey.toLowerCase() + '=' + value.toLowerCase()).replace('+', '.').replace('/', '_') +} + +function partitionByVideoBids(bidRequests) { + return bidRequests.reduce(function (acc, bid) { + // Fallback to banner ads if nothing specified + if (isVideoRequest(bid)) { + acc[0].push(bid); } else { - pdNode = rootNode.appendChild(pdFrame); + acc[1].push(bid); } - } + return acc; + }, [[], []]); +} + +function getMediaTypeFromRequest(serverRequest) { + return /avjp$/.test(serverRequest.url) ? VIDEO : BANNER; +} + +function buildCommonQueryParamsFromBids(bids, bidderRequest) { + const isInIframe = utils.inIframe(); + let defaultParams; + + defaultParams = { + ju: config.getConfig('pageUrl') || utils.getTopWindowUrl(), + jr: utils.getTopWindowReferrer(), + ch: document.charSet || document.characterSet, + res: `${screen.width}x${screen.height}x${screen.colorDepth}`, + ifr: isInIframe, + tz: new Date().getTimezoneOffset(), + tws: getViewportDimensions(isInIframe), + be: 1, + dddid: utils._map(bids, bid => bid.transactionId).join(','), + nocache: new Date().getTime() + }; - function addBidResponse(adUnit, bid) { - let bidResponse = bidfactory.createBid(adUnit ? CONSTANTS.STATUS.GOOD : CONSTANTS.STATUS.NO_BID, bid); - bidResponse.bidderCode = BIDDER_CODE; + if (utils.deepAccess(bidderRequest, 'gdprConsent')) { + let gdprConsentConfig = bidderRequest.gdprConsent; - if (adUnit) { - let creative = adUnit.creative[0]; - bidResponse.ad = adUnit.html; - bidResponse.cpm = Number(adUnit.pub_rev) / 1000; - bidResponse.ad_id = adUnit.adid; - if (adUnit.deal_id) { - bidResponse.dealId = adUnit.deal_id; - } - if (creative) { - bidResponse.width = creative.width; - bidResponse.height = creative.height; - } - if (adUnit.tbd) { - bidResponse.tbd = adUnit.tbd; - } + if (gdprConsentConfig.consentString !== undefined) { + defaultParams.gdpr_consent = gdprConsentConfig.consentString; } - bidmanager.addBidResponse(bid.placementCode, bidResponse); - } - function buildQueryStringFromParams(params) { - for (let key in params) { - if (params.hasOwnProperty(key)) { - if (!params[key]) { - delete params[key]; - } - } + if (gdprConsentConfig.gdprApplies !== undefined) { + defaultParams.gdpr = gdprConsentConfig.gdprApplies ? 1 : 0; + } + + if (config.getConfig('consentManagement.cmpApi') === 'iab') { + defaultParams.x_gdpr_f = 1; } - return utils._map(Object.keys(params), key => `${key}=${params[key]}`) - .join('&'); } - function buildBoPixel(creative, params) { - let img = new Image(); - let recordPixel = creative.tracking.impression; - let boBase = recordPixel.match(/([^?]+\/)ri\?/); + return defaultParams; +} + +function buildOXBannerRequest(bids, bidderRequest) { + let queryParams = buildCommonQueryParamsFromBids(bids, bidderRequest); - if (boBase) { - img.src = `${boBase[1]}bo?${buildQueryStringFromParams(params)}`; + queryParams.auid = utils._map(bids, bid => bid.params.unit).join(','); + queryParams.aus = utils._map(bids, bid => utils.parseSizesInput(bid.sizes).join(',')).join('|'); + queryParams.bc = bids[0].params.bc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`; + + let customParamsForAllBids = []; + let hasCustomParam = false; + bids.forEach(function (bid) { + if (bid.params.customParams) { + let customParamsForBid = utils._map(Object.keys(bid.params.customParams), customKey => formatCustomParms(customKey, bid.params.customParams)); + let formattedCustomParams = window.btoa(customParamsForBid.join('&')); + hasCustomParam = true; + customParamsForAllBids.push(formattedCustomParams); + } else { + customParamsForAllBids.push(''); } + }); + if (hasCustomParam) { + queryParams.tps = customParamsForAllBids.join(','); } - function adUnitHasValidSizeFromBid(adUnit, bid) { - let sizes = utils.parseSizesInput(bid.sizes); - let sizeLength = (sizes && sizes.length) || 0; - let found = false; - let creative = adUnit.creative && adUnit.creative[0]; - let creative_size = String(creative.width) + 'x' + String(creative.height); - - if (utils.isArray(sizes)) { - for (let i = 0; i < sizeLength; i++) { - let size = sizes[i]; - if (String(size) === String(creative_size)) { - found = true; - break; - } - } + let customFloorsForAllBids = []; + let hasCustomFloor = false; + bids.forEach(function (bid) { + if (bid.params.customFloor) { + customFloorsForAllBids.push(bid.params.customFloor * 1000); + hasCustomFloor = true; + } else { + customFloorsForAllBids.push(0); } - return found; + }); + if (hasCustomFloor) { + queryParams.aumfs = customFloorsForAllBids.join(','); } - function formatCustomParms(customKey, customParams) { - let value = customParams[customKey]; - if (Array.isArray(value)) { - // if value is an array, join them with commas first - value = value.join(','); - } - // return customKey=customValue format, escaping + to . and / to _ - return (customKey + '=' + value).replace('+', '.').replace('/', '_') + let url = `//${bids[0].params.delDomain}/w/1.0/arj`; + return { + method: 'GET', + url: url, + data: queryParams, + payload: {'bids': bids, 'startTime': new Date()} + }; +} + +function buildOXVideoRequest(bid, bidderRequest) { + let url = `//${bid.params.delDomain}/v/1.0/avjp`; + let oxVideoParams = generateVideoParameters(bid, bidderRequest); + return { + method: 'GET', + url: url, + data: oxVideoParams, + payload: {'bid': bid, 'startTime': new Date()} + }; +} + +function generateVideoParameters(bid, bidderRequest) { + let queryParams = buildCommonQueryParamsFromBids([bid], bidderRequest); + let oxVideoConfig = utils.deepAccess(bid, 'params.video') || {}; + let context = utils.deepAccess(bid, 'mediaTypes.video.context'); + let playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + let width; + let height; + + // normalize config for video size + if (utils.isArray(bid.sizes) && bid.sizes.length === 2 && !utils.isArray(bid.sizes[0])) { + width = parseInt(bid.sizes[0], 10); + height = parseInt(bid.sizes[1], 10); + } else if (utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { + width = parseInt(bid.sizes[0][0], 10); + height = parseInt(bid.sizes[0][1], 10); + } else if (utils.isArray(playerSize) && playerSize.length === 2) { + width = parseInt(playerSize[0], 10); + height = parseInt(playerSize[1], 10); } - function buildRequest(bids, params, delDomain) { - if (!utils.isArray(bids)) { - return; + Object.keys(oxVideoConfig).forEach(function (key) { + if (key === 'openrtb') { + oxVideoConfig[key].w = width || oxVideoConfig[key].w; + oxVideoConfig[key].v = height || oxVideoConfig[key].v; + queryParams[key] = JSON.stringify(oxVideoConfig[key]); + } else if (!(key in queryParams) && key !== 'url') { + // only allow video-related attributes + queryParams[key] = oxVideoConfig[key]; } + }); - params.auid = utils._map(bids, bid => bid.params.unit).join('%2C'); - params.dddid = utils._map(bids, bid => bid.transactionId).join('%2C'); - params.aus = utils._map(bids, bid => { - return utils.parseSizesInput(bid.sizes).join(','); - }).join('|'); - - let customParamsForAllBids = []; - let hasCustomParam = false; - bids.forEach(function (bid) { - if (bid.params.customParams) { - let customParamsForBid = utils._map(Object.keys(bid.params.customParams), customKey => formatCustomParms(customKey, bid.params.customParams)); - let formattedCustomParams = window.btoa(customParamsForBid.join('&')); - hasCustomParam = true; - customParamsForAllBids.push(formattedCustomParams); - } else { - customParamsForAllBids.push(''); - } - }); - if (hasCustomParam) { - params.tps = customParamsForAllBids.join('%2C'); - } + queryParams.auid = bid.params.unit; + // override prebid config with openx config if available + queryParams.vwd = width || oxVideoConfig.vwd; + queryParams.vht = height || oxVideoConfig.vht; - let customFloorsForAllBids = []; - let hasCustomFloor = false; - bids.forEach(function (bid) { - if (bid.params.customFloor) { - customFloorsForAllBids.push(bid.params.customFloor * 1000); - hasCustomFloor = true; - } else { - customFloorsForAllBids.push(0); - } - }); - if (hasCustomFloor) { - params.aumfs = customFloorsForAllBids.join('%2C'); - } + if (context === 'outstream') { + queryParams.vos = '101'; + } - try { - let queryString = buildQueryStringFromParams(params); - let url = `//${delDomain}/w/1.0/arj?${queryString}`; - ajax.ajax(url, oxARJResponse, void (0), { - withCredentials: true - }); - } catch (err) { - utils.logMessage(`Ajax call failed due to ${err}`); - } + if (oxVideoConfig.mimes) { + queryParams.vmimes = oxVideoConfig.mimes; } - function callBids(params) { - let isIfr; - const bids = params.bids || []; - let currentURL = (window.parent !== window) ? document.referrer : window.location.href; - currentURL = currentURL && encodeURIComponent(currentURL); - try { - isIfr = window.self !== window.top; - } catch (e) { - isIfr = false; - } - if (bids.length === 0) { - return; - } + return queryParams; +} - let delDomain = bids[0].params.delDomain; - let bcOverride = bids[0].params.bc; +function createVideoBidResponses(response, {bid, startTime}) { + let bidResponses = []; - startTime = new Date(params.start); - if (params.timeout) { - timeout = params.timeout; - } - if (bids[0].params.hasOwnProperty('sendBoPixel') && typeof (bids[0].params.sendBoPixel) === 'boolean') { - shouldSendBoPixel = bids[0].params.sendBoPixel; - } + if (response !== undefined && response.vastUrl !== '' && response.pub_rev !== '') { + let vastQueryParams = parse(response.vastUrl).search || {}; + let bidResponse = {}; + bidResponse.requestId = bid.bidId; + bidResponse.bidderCode = BIDDER_CODE; + // default 5 mins + bidResponse.ttl = 300; + // true is net, false is gross + bidResponse.netRevenue = true; + bidResponse.currency = response.currency; + bidResponse.cpm = Number(response.pub_rev) / 1000; + bidResponse.width = response.width; + bidResponse.height = response.height; + bidResponse.creativeId = response.adid; + bidResponse.vastUrl = response.vastUrl; + bidResponse.mediaType = VIDEO; + + // enrich adunit with vast parameters + response.ph = vastQueryParams.ph; + response.colo = vastQueryParams.colo; + response.ts = vastQueryParams.ts; + + bidResponses.push(bidResponse); + + registerBeacon(VIDEO, response, startTime) + } - buildRequest(bids, { - ju: currentURL, - jr: currentURL, - ch: document.charSet || document.characterSet, - res: `${screen.width}x${screen.height}x${screen.colorDepth}`, - ifr: isIfr, - tz: startTime.getTimezoneOffset(), - tws: getViewportDimensions(isIfr), - ef: 'bt%2Cdb', - be: 1, - bc: bcOverride || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, - nocache: new Date().getTime() - }, - delDomain); + return bidResponses; +} + +function registerBeacon(mediaType, adUnit, startTime) { + // only register beacon once + if (!shouldSendBoPixel) { + return; } + shouldSendBoPixel = false; - return { - callBids: callBids + let bt = config.getConfig('bidderTimeout'); + let beaconUrl; + if (window.PREBID_TIMEOUT) { + bt = Math.min(window.PREBID_TIMEOUT, bt); + } + + let beaconParams = { + bd: +(new Date()) - startTime, + bp: adUnit.pub_rev, + br: '0', // may be 0, t, or p + bs: utils.getTopWindowLocation().hostname, + bt: bt, + ts: adUnit.ts }; -}; -adaptermanager.registerBidAdapter(new OpenxAdapter(), 'openx'); + beaconParams.br = beaconParams.bt < beaconParams.bd ? 't' : 'p'; + + if (mediaType === VIDEO) { + let url = parse(adUnit.colo); + beaconParams.ph = adUnit.ph; + beaconUrl = `//${url.hostname}/w/1.0/bo?${buildQueryStringFromParams(beaconParams)}` + } else { + let recordPixel = utils.deepAccess(adUnit, 'creative.0.tracking.impression'); + let boBase = recordPixel.match(/([^?]+\/)ri\?/); + + if (boBase && boBase.length > 1) { + beaconUrl = `${boBase[1]}bo?${buildQueryStringFromParams(beaconParams)}`; + } + } + + if (beaconUrl) { + userSync.registerSync('image', BIDDER_CODE, beaconUrl); + } +} -module.exports = OpenxAdapter; +registerBidder(spec); diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md new file mode 100644 index 00000000000..9e9d3ebfa7a --- /dev/null +++ b/modules/openxBidAdapter.md @@ -0,0 +1,48 @@ +# Overview + +``` +Module Name: OpenX Bidder Adapter +Module Type: Bidder Adapter +Maintainer: team-openx@openx.com +``` + +# Description + +Module that connects to OpenX's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[728, 90]], // a display size + mediaTypes: {'banner': {}}, + bids: [ + { + bidder: 'openx', + params: { + unit: '539439964', + delDomain: 'se-demo-d.openx.net' + } + } + ] + }, + { + code: 'video1', + sizes: [[640,480]], + mediaTypes: {'video': {}}, + bids: [ + { + bidder: 'openx', + params: { + unit: '539131525', + delDomain: 'zdo.com', + video: { + url: 'abc.com' + } + } + } + ] + } + ]; +``` diff --git a/modules/optimaticBidAdapter.js b/modules/optimaticBidAdapter.js new file mode 100644 index 00000000000..0c8305e6867 --- /dev/null +++ b/modules/optimaticBidAdapter.js @@ -0,0 +1,106 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +export const ENDPOINT = '//mg-bid.optimatic.com/adrequest/'; + +export const spec = { + + version: '1.0.4', + + code: 'optimatic', + + supportedMediaTypes: ['video'], + + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.placement && bid.params.bidfloor); + }, + + buildRequests: function(bids) { + return bids.map(bid => { + return { + method: 'POST', + url: ENDPOINT + bid.params.placement, + data: getData(bid), + options: {contentType: 'application/json'}, + bidRequest: bid + } + }) + }, + + interpretResponse: function(response, { bidRequest }) { + let bid; + let size; + let bidResponse; + try { + response = response.body; + bid = response.seatbid[0].bid[0]; + } catch (e) { + response = null; + } + if (!response || !bid || (!bid.adm && !bid.nurl) || !bid.price) { + utils.logWarn(`No valid bids from ${spec.code} bidder`); + return []; + } + size = getSize(bidRequest.sizes); + bidResponse = { + requestId: bidRequest.bidId, + bidderCode: spec.code, + cpm: bid.price, + creativeId: bid.id, + width: size.width, + height: size.height, + mediaType: 'video', + currency: response.cur, + ttl: 300, + netRevenue: true + }; + if (bid.nurl) { + bidResponse.vastUrl = bid.nurl; + } else if (bid.adm) { + bidResponse.vastXml = bid.adm; + } + return bidResponse; + } +}; + +function getSize(sizes) { + let parsedSizes = utils.parseSizesInput(sizes); + let [ width, height ] = parsedSizes.length ? parsedSizes[0].split('x') : []; + return { + width: parseInt(width, 10) || undefined, + height: parseInt(height, 10) || undefined + }; +} + +function getData (bid) { + let size = getSize(bid.sizes); + let loc = utils.getTopWindowLocation(); + let global = (window.top) ? window.top : window; + return { + id: utils.generateUUID(), + imp: [{ + id: '1', + bidfloor: bid.params.bidfloor, + video: { + mimes: ['video/mp4', 'video/ogg', 'video/webm', 'video/x-flv', 'application/javascript', 'application/x-shockwave-flash'], + w: size.width, + h: size.height + } + }], + site: { + id: '1', + domain: loc.host, + page: loc.href, + ref: utils.getTopWindowReferrer(), + publisher: { + id: '1' + } + }, + device: { + ua: global.navigator.userAgent, + ip: '127.0.0.1', + devicetype: 1 + } + }; +} + +registerBidder(spec); diff --git a/modules/optimaticBidAdapter.md b/modules/optimaticBidAdapter.md new file mode 100644 index 00000000000..edaa3da90f6 --- /dev/null +++ b/modules/optimaticBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: Optimatic Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@optimatic.com +``` + +# Description + +Optimatic Bid Adapter Module connects to Optimatic Demand Sources for Video Ads + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[640,480]], // a video size + bids: [ + { + bidder: "optimatic", + params: { + placement: "2chy7Gc2eSQL", + bidfloor: 2.5 + } + } + ] + }, + ]; +``` diff --git a/modules/optimeraBidAdapter.js b/modules/optimeraBidAdapter.js new file mode 100644 index 00000000000..fc3c4c96f1b --- /dev/null +++ b/modules/optimeraBidAdapter.js @@ -0,0 +1,84 @@ +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'optimera'; +const SCORES_BASE_URL = 'https://s3.amazonaws.com/elasticbeanstalk-us-east-1-397719490216/json/client/'; + +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 (bidRequest) { + if (typeof bidRequest.params !== 'undefined' && typeof bidRequest.params.clientID !== 'undefined') { + return true; + } else { + return false; + } + }, + /** + * Make a server request from the list of BidRequests. + * + * We call the existing scores data file for ad slot placement scores. + * These scores will be added to the dealId to be pushed to DFP. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests) { + let optimeraHost = window.location.host; + let optimeraPathName = window.location.pathname; + let timestamp = Math.round(new Date().getTime() / 1000); + if (typeof validBidRequests[0].params.clientID !== 'undefined') { + let clientID = validBidRequests[0].params.clientID; + let scoresURL = SCORES_BASE_URL + clientID + '/' + optimeraHost + optimeraPathName + '.js'; + return { + method: 'GET', + url: scoresURL, + payload: validBidRequests, + data: {'t': timestamp} + }; + } + }, + /** + * Unpack the response from the server into a list of bids. + * + * Some required bid params are not needed for this so default + * values are used. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + let validBids = bidRequest.payload; + let bidResponses = []; + let dealId = ''; + if (typeof serverResponse.body !== 'undefined') { + let scores = serverResponse.body; + for (let i = 0; i < validBids.length; i++) { + if (typeof validBids[i].params.clientID !== 'undefined') { + if (validBids[i].adUnitCode in scores) { + dealId = scores[validBids[i].adUnitCode]; + } + let bidResponse = { + requestId: validBids[i].bidId, + ad: '
', + cpm: 0.01, + width: 0, + height: 0, + dealId: dealId, + ttl: 300, + creativeId: '1', + netRevenue: '0', + currency: 'USD' + }; + bidResponses.push(bidResponse); + } + } + } + return bidResponses; + } +} + +registerBidder(spec); diff --git a/modules/optimeraBidAdapter.md b/modules/optimeraBidAdapter.md new file mode 100644 index 00000000000..8fca42fdac0 --- /dev/null +++ b/modules/optimeraBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Optimera Bidder Adapter +Module Type: Bidder Adapter +Maintainer: kcandiotti@optimera.nyc +``` + +# Description + +Module that adds ad placement visibility scores for DFP. + +# Test Parameters +``` + var adUnits = [{ + code: 'div-1', + sizes: [[300, 250], [300,600]], + bids: [ + { + bidder: 'optimera', + params: { + clientID: '0' + } + }] + },{ + code: 'div-0', + sizes: [[728, 90]], + bids: [ + { + bidder: 'optimera', + params: { + clientID: '0' + } + }] + }]; +``` diff --git a/modules/orbitsoftBidAdapter.js b/modules/orbitsoftBidAdapter.js index e01f82a7097..0ad3c150767 100644 --- a/modules/orbitsoftBidAdapter.js +++ b/modules/orbitsoftBidAdapter.js @@ -1,15 +1,9 @@ -import { getBidRequest } from 'src/utils'; +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import {config} from 'src/config'; -let CONSTANTS = require('src/constants'); -let bidmanager = require('src/bidmanager'); -let bidfactory = require('src/bidfactory'); -let adloader = require('src/adloader'); -let utils = require('src/utils'); -let adaptermanager = require('src/adaptermanager'); -let Adapter = require('src/adapter').default; - -let ORBITSOFT_BIDDERCODE = 'orbitsoft'; -let styleParamsToFieldsMap = { +const BIDDER_CODE = 'orbitsoft'; +let styleParamsMap = { 'title.family': 'f1', // headerFont 'title.size': 'fs1', // headerFontSize 'title.weight': 'w1', // headerWeight @@ -29,200 +23,125 @@ let styleParamsToFieldsMap = { 'colors.border': 'c1', // borderColor 'colors.link': 'c6', // lnkColor }; - -let OrbitsoftAdapter = function OrbitsoftAdapter() { - let baseAdapter = new Adapter(ORBITSOFT_BIDDERCODE); - - baseAdapter.callBids = function(params) { - let bids = params.bids || []; - - for (let i = 0; i < bids.length; i++) { - let bidRequest = bids[i]; - let callbackId = bidRequest.bidId; - let jptCall = buildJPTCall(bidRequest, callbackId); - - if (jptCall) { - adloader.loadScript(jptCall); - } else { - // indicate that there is no bid for this placement - let bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bidRequest); - bid.bidderCode = params.bidderCode; - bidmanager.addBidResponse(bidRequest.placementCode, bid); - } - } - } - - function buildJPTCall(bid, callbackId) { - // Determine tag params - let placementId = utils.getBidIdParameter('placementId', bid.params); - - let referrer = utils.getBidIdParameter('ref', bid.params); - let location = utils.getBidIdParameter('loc', bid.params); - let jptCall = utils.getBidIdParameter('requestUrl', bid.params); - if (jptCall.length === 0) { - // No param requestUrl - // @if NODE_ENV='debug' - utils.logMessage('No param requestUrl'); - // @endif - return null; - } else { - jptCall += '?'; - } - - jptCall = utils.tryAppendQueryString(jptCall, 'callback', '$$PREBID_GLOBAL$$.handleOASCB'); - jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); - jptCall = utils.tryAppendQueryString(jptCall, 'scid', placementId); - - // Sizes takes a bit more logic - let sizeQueryString; - let parsedSizes = utils.parseSizesInput(bid.sizes); - - // Combine string into proper query string - let parsedSizesLength = parsedSizes.length; - if (parsedSizesLength > 0) { - // First value should be "size" - sizeQueryString = 'size=' + parsedSizes[0]; - jptCall += sizeQueryString + '&'; - } - - // Append custom attributes: - let paramsCopy = Object.assign({}, bid.params); - - // Delete attributes already used - delete paramsCopy.placementId; - delete paramsCopy.referrer; - delete paramsCopy.style; - delete paramsCopy.customParams; - - // Get the reminder - jptCall += utils.parseQueryStringParameters(paramsCopy); - - // Append location & referrer - if (location === '') { - location = utils.getTopWindowUrl(); - } - if (referrer === '') { - referrer = window.top.document.referrer; - } - jptCall = utils.tryAppendQueryString(jptCall, 'loc', location); - jptCall = utils.tryAppendQueryString(jptCall, 'ref', referrer); - - // Remove the trailing "&" - jptCall = removeTrailingAmp(jptCall); - - // @if NODE_ENV='debug' - utils.logMessage('jpt request built: ' + jptCall); - // @endif - - // Append a timer here to track latency - bid.startTime = new Date().getTime(); - - return jptCall; - } - - // Remove the trailing "&" - function removeTrailingAmp(url) { - if (url.lastIndexOf('&') === url.length - 1) { - url = url.substring(0, url.length - 1); +export const spec = { + code: BIDDER_CODE, + aliases: ['oas', '152media'], // short code and customer aliases + isBidRequestValid: function (bid) { + switch (true) { + case !('params' in bid): + utils.logError(bid.bidder + ': No required params'); + return false; + case !(bid.params.placementId): + utils.logError(bid.bidder + ': No required param placementId'); + return false; + case !(bid.params.requestUrl): + utils.logError(bid.bidder + ': No required param requestUrl'); + return false; } - return url; - } - - // Expose the callback to the global object - $$PREBID_GLOBAL$$.handleOASCB = function (jptResponseObj) { - let bidCode; - - if (jptResponseObj && jptResponseObj.callback_uid) { - let responseCPM; - let id = jptResponseObj.callback_uid; - let placementCode = ''; - let bidObj = getBidRequest(id); - if (bidObj) { - bidCode = bidObj.bidder; - - placementCode = bidObj.placementCode; - - // Set the status - bidObj.status = CONSTANTS.STATUS.GOOD; + return true; + }, + buildRequests: function (validBidRequests) { + let bidRequest; + let serverRequests = []; + for (let i = 0; i < validBidRequests.length; i++) { + bidRequest = validBidRequests[i]; + let bidRequestParams = bidRequest.params; + let callbackId = utils.getUniqueIdentifierStr(); + let placementId = utils.getBidIdParameter('placementId', bidRequestParams); + let requestUrl = utils.getBidIdParameter('requestUrl', bidRequestParams); + let referrer = utils.getBidIdParameter('ref', bidRequestParams); + let location = utils.getBidIdParameter('loc', bidRequestParams); + // Append location & referrer + if (location === '') { + location = utils.getTopWindowUrl(); + } + if (referrer === '') { + referrer = utils.getTopWindowReferrer(); } - // @if NODE_ENV='debug' - utils.logMessage('JSONP callback function called for ad ID: ' + id); - // @endif - - let bid = []; - if (jptResponseObj.cpm && jptResponseObj.cpm !== 0) { - // Store bid response - responseCPM = jptResponseObj.cpm; - // Bid status is good (indicating 1) - bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD, bidObj); - bid.bidderCode = bidCode; - bid.cpm = responseCPM; - bid.adUrl = jptResponseObj.content_url; - bid.width = jptResponseObj.width; - bid.height = jptResponseObj.height; - - // Styles params - let styles = utils.getBidIdParameter('style', bidObj.params); - let stylesParams = {}; - for (let currentValue in styles) { - if (styles.hasOwnProperty(currentValue)) { - let currentStyle = styles[currentValue]; - for (let field in currentStyle) { - if (currentStyle.hasOwnProperty(field)) { - let styleField = styleParamsToFieldsMap[currentValue + '.' + field]; - if (styleField !== undefined) { - stylesParams[styleField] = currentStyle[field]; - } + // Styles params + let stylesParams = utils.getBidIdParameter('style', bidRequestParams); + let stylesParamsArray = {}; + for (let currentValue in stylesParams) { + if (stylesParams.hasOwnProperty(currentValue)) { + let currentStyle = stylesParams[currentValue]; + for (let field in currentStyle) { + if (currentStyle.hasOwnProperty(field)) { + let styleField = styleParamsMap[currentValue + '.' + field]; + if (typeof styleField !== 'undefined') { + stylesParamsArray[styleField] = currentStyle[field]; } } } } - bid.adUrl += '&' + utils.parseQueryStringParameters(stylesParams); - - // Custom params - let customParams = utils.getBidIdParameter('customParams', bidObj.params); - let customParamsArray = {}; - for (let customField in customParams) { - if (customParams.hasOwnProperty(customField)) { - customParamsArray['c.' + customField] = customParams[customField]; - } - } - let customParamsLink = utils.parseQueryStringParameters(customParamsArray); - if (customParamsLink) { - // Don't append a "&" here, we have already done it in parseQueryStringParameters - bid.adUrl += customParamsLink; + } + // Custom params + let customParams = utils.getBidIdParameter('customParams', bidRequestParams); + let customParamsArray = {}; + for (let customField in customParams) { + if (customParams.hasOwnProperty(customField)) { + customParamsArray['c.' + customField] = customParams[customField]; } - - // Remove the trailing "&" - bid.adUrl = removeTrailingAmp(bid.adUrl); - - bidmanager.addBidResponse(placementCode, bid); - } else { - // No response data - // @if NODE_ENV='debug' - utils.logMessage('No prebid response from Orbitsoft for placement code ' + placementCode); - // @endif - // indicate that there is no bid for this placement - bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bidObj); - bid.bidderCode = bidCode; - bidmanager.addBidResponse(placementCode, bid); } - } else { - // No response data - // @if NODE_ENV='debug' - utils.logMessage('No prebid response for placement'); - // @endif + + // Sizes params (not supports by server, for future features) + let sizesParams = bidRequest.sizes; + let parsedSizes = utils.parseSizesInput(sizesParams); + + serverRequests.push({ + method: 'GET', + url: requestUrl, + data: Object.assign({ + 'scid': placementId, + 'callback_uid': callbackId, + 'loc': location, + 'ref': referrer, + 'size': parsedSizes + }, stylesParamsArray, customParamsArray), + options: {withCredentials: false}, + bidRequest: bidRequest + }); + } + return serverRequests; + }, + interpretResponse: function (serverResponse, request) { + let bidResponses = []; + if (!serverResponse || serverResponse.error) { + utils.logError(BIDDER_CODE + ': Server response error'); + return bidResponses; } - }; - return Object.assign(this, { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode, - buildJPTCall: buildJPTCall - }); -}; + const serverBody = serverResponse.body; + if (!serverBody) { + utils.logError(BIDDER_CODE + ': Empty bid response'); + return bidResponses; + } -adaptermanager.registerBidAdapter(new OrbitsoftAdapter(), ORBITSOFT_BIDDERCODE); + const CPM = serverBody.cpm; + const WIDTH = serverBody.width; + const HEIGHT = serverBody.height; + const CREATIVE = serverBody.content_url; + const CALLBACK_UID = serverBody.callback_uid; + const TIME_TO_LIVE = config.getConfig('_bidderTimeout'); + const REFERER = utils.getTopWindowUrl(); + let bidRequest = request.bidRequest; + if (CPM > 0 && WIDTH > 0 && HEIGHT > 0) { + let bidResponse = { + requestId: bidRequest.bidId, + cpm: CPM, + width: WIDTH, + height: HEIGHT, + creativeId: CALLBACK_UID, + ttl: TIME_TO_LIVE, + referrer: REFERER, + currency: 'USD', + netRevenue: true, + adUrl: CREATIVE + }; + bidResponses.push(bidResponse); + } -module.exports = OrbitsoftAdapter; + return bidResponses; + } +}; +registerBidder(spec); diff --git a/modules/orbitsoftBidAdapter.md b/modules/orbitsoftBidAdapter.md new file mode 100644 index 00000000000..a18f075b6b1 --- /dev/null +++ b/modules/orbitsoftBidAdapter.md @@ -0,0 +1,60 @@ +# Overview + +``` +Module Name: Orbitsoft Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@orbitsoft.com +``` + +# Description + +Module that connects to Orbitsoft's demand sources. The “sizes” option is not supported, and the size of the ad depends on the placement settings. You can use an optional “style” parameter to set the appearance only for text ad. Specify the “requestUrl” param to your Orbitsoft ad server header bidding endpoint. + +# Test Parameters +``` + var adUnits = [ + { + code: 'orbitsoft-div', + bids: [ + { + bidder: "orbitsoft", + params: { + placementId: '132', + requestUrl: 'https://orbitsoft.com/php/ads/hb.php', + style: { + title: { + family: 'Tahoma', + size: 'medium', + weight: 'normal', + style: 'normal', + color: '0053F9' + }, + description: { + family: 'Tahoma', + size: 'medium', + weight: 'normal', + style: 'normal', + color: '0053F9' + }, + url: { + family: 'Tahoma', + size: 'medium', + weight: 'normal', + style: 'normal', + color: '0053F9' + }, + colors: { + background: 'ffffff', + border: 'E0E0E0', + link: '5B99FE' + } + }, + customParams: { + macro_name: "macro_value" + } + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/peak226BidAdapter.js b/modules/peak226BidAdapter.js new file mode 100644 index 00000000000..4f4ee2f97ff --- /dev/null +++ b/modules/peak226BidAdapter.js @@ -0,0 +1,97 @@ +import { registerBidder } from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; +import { getTopWindowUrl, logWarn } from 'src/utils'; + +const BIDDER_CODE = 'peak226'; +const URL = '//a.ad216.com/header_bid'; + +export const spec = { + code: BIDDER_CODE, + + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + const { params } = bid; + + return !!params.uid; + }, + + buildRequests: function (validBidRequests) { + const bidsMap = validBidRequests.reduce((res, bid) => { + const { uid } = bid.params; + + res[uid] = res[uid] || []; + res[uid].push(bid); + + return res; + }, {}); + + return { + method: 'GET', + url: + URL + + toQueryString({ + u: getTopWindowUrl(), + auids: Object.keys(bidsMap).join(',') + }), + bidsMap + }; + }, + + interpretResponse: function (serverResponse, { bidsMap }) { + const response = serverResponse.body; + const bidResponses = []; + + if (!response) { + logWarn(`No response from ${spec.code} bidder`); + + return bidResponses; + } + + if (!response.seatbid || !response.seatbid.length) { + logWarn(`No seatbid in response from ${spec.code} bidder`); + + return bidResponses; + } + + response.seatbid.forEach((seatbid, i) => { + if (!seatbid.bid || !seatbid.bid.length) { + logWarn(`No bid in seatbid[${i}] response from ${spec.code} bidder`); + return; + } + seatbid.bid.forEach(responseBid => { + const requestBids = bidsMap[responseBid.auid]; + + requestBids.forEach(requestBid => { + bidResponses.push({ + requestId: requestBid.bidId, + bidderCode: spec.code, + width: responseBid.w, + height: responseBid.h, + mediaType: BANNER, + creativeId: responseBid.auid, + ad: responseBid.adm, + cpm: responseBid.price, + currency: 'USD', + netRevenue: true, + ttl: 360 + }); + }); + }); + }); + + return bidResponses; + } +}; + +function toQueryString(obj) { + return Object.keys(obj).reduce( + (str, key, i) => + typeof obj[key] === 'undefined' || obj[key] === '' + ? str + : `${str}${str ? '&' : '?'}${key}=${encodeURIComponent(obj[key])}`, + '' + ); +} + +registerBidder(spec); diff --git a/modules/peak226BidAdapter.md b/modules/peak226BidAdapter.md new file mode 100644 index 00000000000..bae15d6c99f --- /dev/null +++ b/modules/peak226BidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Peak226 Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@edge226.com +``` + +# Description + +Module that connects to Peak226's demand sources + +# Test Parameters + +``` + var adUnits = [ + { + code: "test-div", + sizes: [[300, 250]], + mediaType: "banner", + bids: [ + { + bidder: "peak226", + params: { + uid: 76131369 + } + } + ] + } + ]; +``` diff --git a/modules/piximediaBidAdapter.js b/modules/piximediaBidAdapter.js deleted file mode 100644 index 48d2c9ee37b..00000000000 --- a/modules/piximediaBidAdapter.js +++ /dev/null @@ -1,155 +0,0 @@ -var CONSTANTS = require('src/constants.json'); -var utils = require('src/utils.js'); -var bidmanager = require('src/bidmanager.js'); -var bidfactory = require('src/bidfactory.js'); -var adloader = require('src/adloader.js'); -var Adapter = require('src/adapter.js').default; -var adaptermanager = require('src/adaptermanager'); - -var PiximediaAdapter = function PiximediaAdapter() { - var PREBID_URL = '//static.adserver.pm/prebid'; - var baseAdapter = new Adapter('piximedia'); - var bidStash = {}; - - var tryAppendPixiQueryString = function(url, name, value) { - return url + '/' + encodeURIComponent(name) + '=' + value; - }; - - baseAdapter.callBids = function callBidsPiximedia(params) { - utils._each(params.bids, function(bid) { - // valid bids must include a siteId and an placementId - if (bid.placementCode && bid.sizes && bid.params && bid.params.siteId && bid.params.placementId) { - var sizes = bid.params.hasOwnProperty('sizes') ? bid.params.sizes : bid.sizes; - sizes = utils.parseSizesInput(sizes); - - var cbid = utils.getUniqueIdentifierStr(); - - // we allow overriding the URL in the params - var url = bid.params.prebidUrl || PREBID_URL; - - // params are passed to the Piximedia endpoint, including custom params - for (var key in bid.params) { - /* istanbul ignore else */ - if (bid.params.hasOwnProperty(key)) { - var value = bid.params[key]; - switch (key) { - case 'siteId': - url = tryAppendPixiQueryString(url, 'site_id', encodeURIComponent(value)); - break; - - case 'placementId': - url = tryAppendPixiQueryString(url, 'placement_id', encodeURIComponent(value)); - break; - - case 'dealId': - url = tryAppendPixiQueryString(url, 'l_id', encodeURIComponent(value)); - break; - - case 'sizes': - case 'prebidUrl': - break; - - default: - if (typeof value === 'function') { - url = tryAppendPixiQueryString(url, key, encodeURIComponent((value(baseAdapter, params, bid) || '').toString())); - } else { - url = tryAppendPixiQueryString(url, key, encodeURIComponent((value || '').toString())); - } - break; - } - } - } - - url = tryAppendPixiQueryString(url, 'jsonp', '$$PREBID_GLOBAL$$.handlePiximediaCallback'); - url = tryAppendPixiQueryString(url, 'sizes', encodeURIComponent(sizes.join(','))); - url = tryAppendPixiQueryString(url, 'cbid', encodeURIComponent(cbid)); - url = tryAppendPixiQueryString(url, 'rand', String(Math.floor(Math.random() * 1000000000))); - - bidStash[cbid] = { - 'bidObj': bid, - 'url': url, - 'start': new Date().getTime() - }; - utils.logMessage('[Piximedia] Dispatching header Piximedia to URL ' + url); - adloader.loadScript(url); - } - }); - }; - - /* - * Piximedia's bidResponse should look like: - * - * { - * 'foundbypm': true, // a Boolean, indicating if an ad was found - * 'currency': 'EUR', // the currency, as a String - * 'cpm': 1.99, // the win price as a Number, in currency - * 'dealId': null, // or string value of winning deal ID - * 'width': 300, // width in pixels of winning ad - * 'height': 250, // height in pixels of winning ad - * 'html': 'tag_here' // HTML tag to load if we are picked - * } - * - */ - $$PREBID_GLOBAL$$.handlePiximediaCallback = function(bidResponse) { - if (bidResponse && bidResponse.hasOwnProperty('foundbypm')) { - var stash = bidStash[bidResponse.cbid]; // retrieve our stashed data, by using the cbid - var bid; - - if (stash) { - var bidObj = stash.bidObj; - var timelapsed = new Date().getTime(); - timelapsed = timelapsed - stash.start; - - if (bidResponse.foundbypm && bidResponse.width && bidResponse.height && bidResponse.html && bidResponse.cpm && bidResponse.currency) { - /* we have a valid ad to display */ - bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD); - bid.bidderCode = bidObj.bidder; - bid.width = bidResponse.width; - bid.height = bidResponse.height; - bid.ad = bidResponse.html; - bid.cpm = bidResponse.cpm; - bid.currency = bidResponse.currency; - - if (bidResponse.dealId) { - bid.dealId = bidResponse.dealId; - } else { - bid.dealId = null; - } - - bidmanager.addBidResponse(bidObj.placementCode, bid); - - utils.logMessage('[Piximedia] Registered bidresponse from URL ' + stash.url + - ' (time: ' + String(timelapsed) + ')'); - utils.logMessage('[Piximedia] ======> BID placementCode: ' + bidObj.placementCode + - ' CPM: ' + String(bid.cpm) + ' ' + bid.currency + - ' Format: ' + String(bid.width) + 'x' + String(bid.height)); - } else { - /* we have no ads to display */ - bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); - bid.bidderCode = bidObj.bidder; - bidmanager.addBidResponse(bidObj.placementCode, bid); - - utils.logMessage('[Piximedia] Registered BLANK bidresponse from URL ' + stash.url + - ' (time: ' + String(timelapsed) + ')'); - utils.logMessage('[Piximedia] ======> NOBID placementCode: ' + bidObj.placementCode); - } - - // We should no longer need this stashed object, so drop reference: - bidStash[bidResponse.cbid] = null; - } else { - utils.logMessage("[Piximedia] Couldn't find stash for cbid=" + bidResponse.cbid); - } - } - }; - - // return an object with PiximediaAdapter methods - return Object.assign(this, { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode, - getBidderCode: baseAdapter.getBidderCode - }); -}; - -adaptermanager.registerBidAdapter(new PiximediaAdapter(), 'piximedia'); - -module.exports = PiximediaAdapter; diff --git a/modules/platformioBidAdapter.js b/modules/platformioBidAdapter.js index 87ebb2b61c4..b33aeab8f88 100644 --- a/modules/platformioBidAdapter.js +++ b/modules/platformioBidAdapter.js @@ -1,63 +1,306 @@ -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader.js'); -var utils = require('src/utils.js'); -var CONSTANTS = require('src/constants.json'); -var adaptermanager = require('src/adaptermanager'); - -var PlatformIOAdapter = function PlatformIOAdapter() { - function _callBids(params) { - var bidURL; - var bids = params.bids || []; - var requestURL = window.location.protocol + '//js.adx1.com/pb_ortb.js?cb=' + new Date().getTime() + '&ver=1&'; - - for (var i = 0; i < bids.length; i++) { - var requestParams = {}; - var bid = bids[i]; - - requestParams.pub_id = bid.params.pubId; - requestParams.site_id = bid.params.siteId; - requestParams.placement_id = bid.placementCode; - - var parseSized = utils.parseSizesInput(bid.sizes); - var arrSize = parseSized[0].split('x'); - - requestParams.width = arrSize[0]; - requestParams.height = arrSize[1]; - requestParams.callback = '$$PREBID_GLOBAL$$._doPlatformIOCallback'; - requestParams.callback_uid = bid.bidId; - bidURL = requestURL + utils.parseQueryStringParameters(requestParams); - - utils.logMessage('PlatformIO.prebid, Bid ID: ' + bid.bidId + ', Pub ID: ' + bid.params.pubId); - adloader.loadScript(bidURL); - } - } - - $$PREBID_GLOBAL$$._doPlatformIOCallback = function (response) { - var bidObject; - var bidRequest; - var callbackID; - callbackID = response.callback_uid; - bidRequest = utils.getBidRequest(callbackID); - if (response.cpm > 0) { - bidObject = bidfactory.createBid(CONSTANTS.STATUS.GOOD, bidRequest); - bidObject.bidderCode = 'platformio'; - bidObject.cpm = response.cpm; - bidObject.ad = response.tag; - bidObject.width = response.width; - bidObject.height = response.height; - } else { - bidObject = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bidRequest); - bidObject.bidderCode = 'platformio'; - utils.logMessage('No Bid response from Platformio request: ' + callbackID); - } - bidmanager.addBidResponse(bidRequest.placementCode, bidObject); +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 NATIVE_DEFAULTS = { + TITLE_LEN: 100, + DESCR_LEN: 200, + SPONSORED_BY_LEN: 50, + IMG_MIN: 150, + ICON_MIN: 50, +}; +const DEFAULT_MIMES = ['video/mp4', 'video/webm', 'application/x-shockwave-flash', 'application/javascript']; +const VIDEO_TARGETING = ['mimes', 'skippable', 'playback_method', 'protocols', 'api']; +const DEFAULT_PROTOCOLS = [2, 3, 5, 6]; +const DEFAULT_APIS = [1, 2]; + +export const spec = { + + code: 'platformio', + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + + isBidRequestValid: bid => ( + !!(bid && bid.params && bid.params.pubId && bid.params.placementId) + ), + buildRequests: bidRequests => { + const request = { + id: bidRequests[0].bidderRequestId, + at: 2, + imp: bidRequests.map(slot => impression(slot)), + site: site(bidRequests), + app: app(bidRequests), + device: device(bidRequests), + }; + return { + method: 'POST', + url: '//piohbdisp.hb.adx1.com/', + data: JSON.stringify(request), + }; + }, + interpretResponse: (response, request) => ( + bidResponseAvailable(request, response.body) + ), +}; + +function bidResponseAvailable(bidRequest, bidResponse) { + const idToImpMap = {}; + const idToBidMap = {}; + const ortbRequest = parse(bidRequest.data); + ortbRequest.imp.forEach(imp => { + idToImpMap[imp.id] = imp; + }); + if (bidResponse) { + bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => { + idToBidMap[bid.impid] = bid; + })); + } + const bids = []; + Object.keys(idToImpMap).forEach(id => { + if (idToBidMap[id]) { + const bid = {}; + bid.requestId = id; + bid.adId = id; + bid.creativeId = id; + bid.cpm = idToBidMap[id].price; + bid.currency = bidResponse.cur; + bid.ttl = 360; + bid.netRevenue = true; + if (idToImpMap[id]['native']) { + bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); + let nurl = idToBidMap[id].nurl; + nurl = nurl.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); + bid['native']['impressionTrackers'] = [nurl]; + bid.mediaType = 'native'; + } else if (idToImpMap[id]['video']) { + bid.vastUrl = idToBidMap[id].adm; + bid.vastUrl = bid.vastUrl.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); + bid.crid = idToBidMap[id].crid; + bid.width = idToImpMap[id].video.w; + bid.height = idToImpMap[id].video.h; + bid.mediaType = 'video'; + } else if (idToImpMap[id]['banner']) { + bid.ad = idToBidMap[id].adm; + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_AD_ID(%7D|\})/gi, idToBidMap[id].adid); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); + bid.width = idToImpMap[id].banner.w; + bid.height = idToImpMap[id].banner.h; + bid.mediaType = 'banner'; + } + bids.push(bid); + } + }); + return bids; +} +function impression(slot) { + return { + id: slot.bidId, + secure: window.location.protocol === 'https:' ? 1 : 0, + 'banner': banner(slot), + 'native': nativeImpression(slot), + 'video': videoImpression(slot), + bidfloor: slot.params.bidFloor || '0.000001', + tagid: slot.params.placementId.toString(), }; +} +function getSizes(slot) { + if (slot.params.size) { + const size = slot.params.size.toUpperCase().split('X'); + return { + width: parseInt(size[0]), + height: parseInt(size[1]), + }; + } return { - callBids: _callBids + width: 1, + height: 1, }; -}; -adaptermanager.registerBidAdapter(new PlatformIOAdapter(), 'platformio'); +} + +function banner(slot) { + if (slot.mediaType === 'banner' || utils.deepAccess(slot, 'mediaTypes.banner')) { + const sizes = getSizes(slot); + return { + w: sizes.width, + h: sizes.height, + }; + } + return null; +} + +function videoImpression(slot) { + if (slot.mediaType === 'video' || utils.deepAccess(slot, 'mediaTypes.video')) { + const sizes = getSizes(slot); + const video = { + w: sizes.width, + h: sizes.height, + mimes: DEFAULT_MIMES, + protocols: DEFAULT_PROTOCOLS, + api: DEFAULT_APIS, + }; + if (slot.params.video) { + Object.keys(slot.params.video).filter(param => includes(VIDEO_TARGETING, param)).forEach(param => video[param] = slot.params.video[param]); + } + return video; + } + return null; +} + +function nativeImpression(slot) { + if (slot.mediaType === 'native' || utils.deepAccess(slot, 'mediaTypes.native')) { + const assets = []; + addAsset(assets, titleAsset(1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN)); + addAsset(assets, dataAsset(2, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN)); + addAsset(assets, dataAsset(3, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN)); + addAsset(assets, imageAsset(4, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN)); + addAsset(assets, imageAsset(5, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN)); + return { + request: JSON.stringify({ assets }), + ver: '1.1', + }; + } + return null; +} + +function addAsset(assets, asset) { + if (asset) { + assets.push(asset); + } +} + +function titleAsset(id, params, defaultLen) { + if (params) { + return { + id, + required: params.required ? 1 : 0, + title: { + len: params.len || defaultLen, + }, + }; + } + return null; +} + +function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { + return params ? { + id, + required: params.required ? 1 : 0, + img: { + type, + wmin: params.wmin || defaultMinWidth, + hmin: params.hmin || defaultMinHeight, + } + } : null; +} + +function dataAsset(id, params, type, defaultLen) { + return params ? { + id, + required: params.required ? 1 : 0, + data: { + type, + len: params.len || defaultLen, + } + } : null; +} + +function site(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; + const siteId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.siteId : '0'; + const appParams = bidderRequest[0].params.app; + if (!appParams) { + return { + publisher: { + id: pubId.toString(), + domain: utils.getTopWindowLocation().hostname, + }, + id: siteId.toString(), + ref: utils.getTopWindowReferrer(), + page: utils.getTopWindowLocation().href, + } + } + return null; +} + +function app(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; + const appParams = bidderRequest[0].params.app; + if (appParams) { + return { + publisher: { + id: pubId.toString(), + }, + id: appParams.id, + name: appParams.name, + bundle: appParams.bundle, + storeurl: appParams.storeUrl, + domain: appParams.domain, + } + } + return null; +} + +function device(bidderRequest) { + const lat = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.latitude : ''; + const lon = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.longitude : ''; + const ifa = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.ifa : ''; + return { + dnt: utils.getDNT() ? 1 : 0, + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + w: (window.screen.width || window.innerWidth), + h: (window.screen.height || window.innerHeigh), + geo: { + lat: lat, + lon: lon, + }, + ifa: ifa, + }; +} + +function parse(rawResponse) { + try { + if (rawResponse) { + return JSON.parse(rawResponse); + } + } catch (ex) { + logError('platformio.parse', 'ERROR', ex); + } + return null; +} + +function nativeResponse(imp, bid) { + if (imp['native']) { + const nativeAd = parse(bid.adm); + const keys = {}; + keys.image = {}; + keys.icon = {}; + if (nativeAd && nativeAd['native'] && nativeAd['native'].assets) { + nativeAd['native'].assets.forEach(asset => { + keys.title = asset.title ? asset.title.text : keys.title; + keys.body = asset.data && asset.id === 2 ? asset.data.value : keys.body; + keys.sponsoredBy = asset.data && asset.id === 3 ? asset.data.value : keys.sponsoredBy; + keys.icon.url = asset.img && asset.id === 4 ? asset.img.url : keys.icon.url; + keys.icon.width = asset.img && asset.id === 4 ? asset.img.w : keys.icon.width; + keys.icon.height = asset.img && asset.id === 4 ? asset.img.h : keys.icon.height; + keys.image.url = asset.img && asset.id === 5 ? asset.img.url : keys.image.url; + keys.image.width = asset.img && asset.id === 5 ? asset.img.w : keys.image.width; + keys.image.height = asset.img && asset.id === 5 ? asset.img.h : keys.image.height; + }); + if (nativeAd['native'].link) { + keys.clickUrl = encodeURIComponent(nativeAd['native'].link.url); + } + return keys; + } + } + return null; +} -module.exports = PlatformIOAdapter; +registerBidder(spec); diff --git a/modules/platformioBidAdapter.md b/modules/platformioBidAdapter.md new file mode 100644 index 00000000000..ff6335d1d70 --- /dev/null +++ b/modules/platformioBidAdapter.md @@ -0,0 +1,89 @@ +# Overview + +**Module Name**: Platform.io Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: siarhei.kasukhin@platform.io + +# Description + +Connects to Platform.io demand source to fetch bids. +Banner, Native, Video formats are supported. +Please use ```platformio``` as the bidder code. + +# Test Parameters +``` + var adUnits = [{ + code: 'dfp-native-div', + mediaType: 'native', + mediaTypes: { + native: { + title: { + required: true, + len: 75 + }, + image: { + required: true + }, + body: { + len: 200 + }, + icon: { + required: false + } + } + }, + bids: [{ + bidder: 'platformio', + params: { + pubId: '29521', + siteId: '26048', + placementId: '123', + bidFloor: '0.001', // optional + ifa: 'XXX-XXX', // optional + latitude: '40.712775', // optional + longitude: '-74.005973', // optional + } + }] + }, + { + code: 'dfp-banner-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ], + } + }, + bids: [{ + bidder: 'platformio', + params: { + pubId: '29521', + siteId: '26049', + size: '300X250', + placementId: '123', + } + }] + }, + { + code: 'dfp-video-div', + sizes: [640, 480], + mediaTypes: { + video: { + context: "instream" + } + }, + bids: [{ + bidder: 'platformio', + params: { + pubId: '29521', + siteId: '26049', + size: '640X480', + placementId: '123', + video: { + skipppable: true, + } + } + }] + } + ]; +``` diff --git a/modules/polluxBidAdapter.js b/modules/polluxBidAdapter.js index 54c2122ec36..463de07341c 100644 --- a/modules/polluxBidAdapter.js +++ b/modules/polluxBidAdapter.js @@ -1,97 +1,120 @@ -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; -import adloader from 'src/adloader'; -import adaptermanager from 'src/adaptermanager'; -import { STATUS } from 'src/constants'; +import { registerBidder } from 'src/adapters/bidderFactory'; -// Prebid adapter for Pollux header bidding client -function PolluxBidAdapter() { - function _callBids(params) { - var bidderUrl = (window.location.protocol) + '//adn.plxnt.com/prebid'; - var bids = params.bids || []; - for (var i = 0; i < bids.length; i++) { - var request_obj = {}; - var bid = bids[i]; - // check params - if (bid.params.zone) { - var domain = utils.getParameterByName('domain'); - var tracker2 = utils.getParameterByName('tracker2'); - if (domain) { - request_obj.domain = domain; - } else { - request_obj.domain = window.location.host; - } - if (tracker2) { - request_obj.tracker2 = tracker2; - } - request_obj.zone = bid.params.zone; - } else { - utils.logError('required param "zone" is missing', 'polluxHandler'); - continue; - } - var parsedSizes = utils.parseSizesInput(bid.sizes); - var parsedSizesLength = parsedSizes.length; - if (parsedSizesLength > 0) { - // first value should be "size" - request_obj.size = parsedSizes[0]; - if (parsedSizesLength > 1) { - // any subsequent values should be "promo_sizes" - var promo_sizes = []; - for (var j = 1; j < parsedSizesLength; j++) { - promo_sizes.push(parsedSizes[j]); - } - request_obj.promo_sizes = promo_sizes.join(','); - } - } - // detect urls - request_obj.callback_id = bid.bidId; - // set a different url bidder - if (bid.bidderUrl) { - bidderUrl = bid.bidderUrl; +const BIDDER_CODE = 'pollux'; +const PLX_ENDPOINT_URL = '//adn.plxnt.com/prebid/v1'; +const PLX_CURRENCY = 'EUR'; +const PLX_TTL = 3600; +const PLX_NETREVENUE = true; + +export const spec = { + code: BIDDER_CODE, + aliases: ['plx'], + /** + * 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.hasOwnProperty('params') || !bid.params.hasOwnProperty('zone')) { + utils.logError('required param "zone" is missing for == ' + BIDDER_CODE + ' =='); + 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) { + if (!Array.isArray(validBidRequests) || !validBidRequests.length) { + return []; + } + const payload = []; + let custom_url = null; + for (let i = 0; i < validBidRequests.length; i++) { + const bid = validBidRequests[i]; + const request = { + bidId: bid.bidId, + zones: bid.params.zone, + sizes: bid.sizes + }; + if (bid.bidderUrl && !custom_url) { + custom_url = bid.bidderUrl; } - var prebidUrl = bidderUrl + '?' + utils.parseQueryStringParameters(request_obj); - utils.logMessage('Pollux request built: ' + prebidUrl); - adloader.loadScript(prebidUrl, null, true); + payload.push(request); } - } - - // expose the callback to global object - function _polluxHandler (response) { - // pollux handler - var bidObject = {}; - var callback_id = response.callback_id; - var placementCode = ''; - var bidObj = utils.getBidRequest(callback_id); - if (bidObj) { - placementCode = bidObj.placementCode; + const payloadString = JSON.stringify(payload); + // build url parameters + const domain = utils.getParameterByName('domain'); + const tracker2 = utils.getParameterByName('tracker2'); + const url_params = {}; + if (domain) { + url_params.domain = domain; + } else { + url_params.domain = utils.getTopWindowUrl(); + } + if (tracker2) { + url_params.tracker2 = tracker2; + } + // build url + let bidder_url = custom_url || PLX_ENDPOINT_URL; + if (url_params) { + bidder_url = bidder_url + '?' + utils.parseQueryStringParameters(url_params); + } + utils.logMessage('== ' + BIDDER_CODE + ' == request built: ' + bidder_url); + return { + method: 'POST', + url: bidder_url, + 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) { + let bidResponses = []; + if (!serverResponse || (typeof serverResponse === 'object' && !serverResponse.hasOwnProperty('body'))) { + utils.logMessage('No prebid response from == ' + BIDDER_CODE + ' == for bid requests:'); + utils.logMessage(bidRequest); + return bidResponses; } - if (bidObj && response.cpm > 0 && !!response.ad) { - bidObject = bidfactory.createBid(STATUS.GOOD, bidObj); - bidObject.bidderCode = bidObj.bidder; - bidObject.mediaType = response.mediaType; - bidObject.cpm = parseFloat(response.cpm); - if (response.ad_type === 'url') { - bidObject.adUrl = response.ad; + serverResponse = serverResponse.body; + if (!Array.isArray(serverResponse) || !serverResponse.length) { + utils.logMessage('No prebid response from == ' + BIDDER_CODE + ' == for bid requests:'); + utils.logMessage(bidRequest); + return bidResponses; + } + // loop through serverResponses + for (let b in serverResponse) { + let bid = serverResponse[b]; + const bidResponse = { + requestId: bid.bidId, // not request id, it's bid's id + cpm: parseFloat(bid.cpm), + width: parseInt(bid.width), + height: parseInt(bid.height), + ttl: PLX_TTL, + creativeId: bid.creativeId, + netRevenue: PLX_NETREVENUE, + currency: PLX_CURRENCY + }; + if (bid.ad_type === 'url') { + bidResponse.adUrl = bid.ad; } else { - bidObject.ad = response.ad; + bidResponse.ad = bid.ad; } - bidObject.width = response.width; - bidObject.height = response.height; - } else { - bidObject = bidfactory.createBid(STATUS.NO_BID, bidObj); - bidObject.bidderCode = 'pollux'; - utils.logMessage('No prebid response from polluxHandler for placement code ' + placementCode); + if (bid.referrer) { + bidResponse.referrer = bid.referrer; + } + bidResponses.push(bidResponse); } - bidmanager.addBidResponse(placementCode, bidObject); - }; - $$PREBID_GLOBAL$$.polluxHandler = _polluxHandler; - // Export the `callBids` function, so that Prebid.js can execute - // this function when the page asks to send out bid requests. - return { - callBids: _callBids, - polluxHandler: _polluxHandler - }; + return bidResponses; + } }; -adaptermanager.registerBidAdapter(new PolluxBidAdapter(), 'pollux'); -module.exports = PolluxBidAdapter; +registerBidder(spec); diff --git a/modules/polluxBidAdapter.md b/modules/polluxBidAdapter.md new file mode 100644 index 00000000000..79bf84e79b9 --- /dev/null +++ b/modules/polluxBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +**Module Name**: Pollux Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: tech@polluxnetwork.com + +# Description + +Module that connects to Pollux Network LLC demand source to fetch bids. +All bids will present CPM in EUR (Euro). + +# Test Parameters +``` + var adUnits = [{ + code: '34f724kh32', + sizes: [[300, 250]], // a single size + bids: [{ + bidder: 'pollux', + params: { + zone: '1806' // a single zone + } + }] + },{ + code: '34f789r783', + sizes: [[300, 250], [728, 90]], // multiple sizes + bids: [{ + bidder: 'pollux', + params: { + zone: '1806,276' // multiple zones, max 5 + } + }] + }]; +``` diff --git a/modules/pre1api.js b/modules/pre1api.js new file mode 100644 index 00000000000..a8aa1f31e70 --- /dev/null +++ b/modules/pre1api.js @@ -0,0 +1,156 @@ + +/** + pre1api module + + This module supports backwards compatibility for those who need extra time to re-code their pages to work with the + Prebid 1.0 API. Use of this backwards compatibility module is recommended only as an interim solution. + + It provides equivalents for the following variables and functions that were deprecated in PBJS 1.0: + - pbjs._winningBids + - pbjs._bidsReceived + - pbjs._bidsRequested + - pbjs._adUnitCodes + - pbjs._adsReceived + - pbjs.cbTimeout + - pbjs.addCallback() + - pbjs.removeCallback() + - pbjs.allBidsAvailable() + - pbjs.bidderTimeout + - pbjs.logging + - pbjs.publisherDomain + - pbjs.setPriceGranularity() + - pbjs.enableSendAllBids() // and also defaults this value to `false` like pre-1.0 + - pbjs.setBidderSequence() + - pbjs.setS2SConfig() // and makes endpoints optional again (defaulting to the appnexus endpoints) + + This will not support the pre-1.0 sizeMapping feature. + + The drawback is that this module disables concurrency for requestBids(), queueing them as was done in pre-1.0. Anytime + an auction request is queued or one of these APIs is accessed it will display a deprecation warning in the console if + logging is enabled. So while this is useful for those that need more time to migrate, it eliminates one of the best + features of PBJS 1.0 as is required to emulate the old API. + */ + +import {config} from 'src/config'; +import {logWarn, logInfo} from 'src/utils'; + +const MODULE_NAME = 'pre-1.0 API'; + +let pbjs = window['$$PREBID_GLOBAL$$']; + +logInfo(`loading ${MODULE_NAME} module and patching prebid with deprecated APIs.`); + +let auctionQueue = []; + +let emptyFn = () => []; + +Object.defineProperty(pbjs, '_winningBids', { + get: () => pbjs.getAllWinningBids() +}); + +let auctionPropMap = { + _bidsReceived: auction => auction.getBidsReceived(), + _bidsRequested: auction => auction.getBidRequests(), + _adUnitCodes: auction => auction.getAdUnitCodes(), + allBidsAvailable: auction => auction.getBidRequests().every((bidRequest) => bidRequest.doneCbCallCount >= 1) +}; + +let configPropMap = { + cbTimeout: 'bidderTimeout', + bidderTimeout: 'bidderTimeout', + logging: 'debug', + publisherDomain: 'publisherDomain', + enableSendAllBids: 'enableSendAllBids', + setPriceGranularity: 'priceGranularity', + setBidderSequence: 'bidderSequence', + setS2SConfig: 's2sConfig' +}; + +pbjs.addCallback = pbjs.onEvent; +pbjs.removeCallback = pbjs.offEvent; + +// can't see anywhere that this was used, but it is listed in Prebid 1.0 transition guide... +// so just adding as empty array +pbjs._adsReceived = []; + +config.setDefaults({ + enableSendAllBids: false, + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } +}); + +let currAuction = { + getBidsReceived: emptyFn, + getBidsRequested: emptyFn, + getAdUnitCodes: emptyFn, + getTimeout: () => config.getConfig('bidderTimeout') +}; + +// we need to intercept s2sConfig rather than call setConfig or setDefaults directly, otherwise the code will fail when +// the server adapter attempts to validate the configuration passed in by the publisher +config.setConfig.addHook((config, next) => { + if (config.s2sConfig) { + config.s2sConfig = Object.assign({ + endpoint: 'https://prebid.adnxs.com/pbs/v1/auction', + syncEndpoint: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' + }, config.s2sConfig); + } + next(config); +}); + +/** + * Hook to queue and disallow concurrent auctions (as Prebid would function pre 1.0) + */ +pbjs.requestBids.addHook((config, next = config) => { + auctionQueue.push(() => { + let oldHandler = config.bidsBackHandler; + config.bidsBackHandler = (...args) => { + if (typeof oldHandler === 'function') { + oldHandler.apply(null, args); + } + + auctionQueue.shift(); + if (auctionQueue[0]) { + auctionQueue[0](); + } + }; + + currAuction = next(config); + }); + + if (auctionQueue.length === 1) { + auctionQueue[0](); + } else { + logWarn(`${MODULE_NAME} module: concurrency has been disabled and "$$PREBID_GLOBAL$$.requestBids" call was queued`); + } +}, 5); + +Object.keys(auctionPropMap).forEach(prop => { + if (prop === 'allBidsAvailable') { + pbjs[prop] = deprecated(prop, () => auctionPropMap[prop](currAuction)); + } + Object.defineProperty(pbjs, prop, { + get: deprecated(prop, () => auctionPropMap[prop](currAuction)) + }); +}); + +Object.keys(configPropMap).forEach(prop => { + if (prop === 'enableSendAllBids') { + pbjs[prop] = deprecated(prop, () => config.setConfig({[prop]: true})); + } else if (prop.lastIndexOf('set', 0) === 0) { + pbjs[prop] = deprecated(prop, value => config.setConfig({[configPropMap[prop]]: value})); + } else { + Object.defineProperty(pbjs, prop, { + get: deprecated(prop, () => config.getConfig(configPropMap[prop])), + set: deprecated(prop, value => config.setConfig({[configPropMap[prop]]: value})) + }); + } +}); + +function deprecated(name, fn) { + return (...args) => { + logWarn(`${MODULE_NAME} module: accessed deprecated API "$$PREBID_GLOBAL$$.${name}"`); + return fn.apply(null, args); + }; +} diff --git a/modules/prebidServerBidAdapter.js b/modules/prebidServerBidAdapter.js index 0906a1a0b3d..f499f5a0ae4 100644 --- a/modules/prebidServerBidAdapter.js +++ b/modules/prebidServerBidAdapter.js @@ -1,18 +1,167 @@ import Adapter from 'src/adapter'; import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; import { ajax } from 'src/ajax'; import { STATUS, S2S } from 'src/constants'; import { cookieSet } from 'src/cookie.js'; import adaptermanager from 'src/adaptermanager'; import { config } from 'src/config'; +import { VIDEO } from 'src/mediaTypes'; +import { isValid } from 'src/adapters/bidderFactory'; +import includes from 'core-js/library/fn/array/includes'; const getConfig = config.getConfig; const TYPE = S2S.SRC; -const cookieSetUrl = 'https://acdn.adnxs.com/cookieset/cs.js'; let _synced = false; +const DEFAULT_S2S_TTL = 60; +const DEFAULT_S2S_CURRENCY = 'USD'; +const DEFAULT_S2S_NETREVENUE = true; + +let _s2sConfig; + +const s2sDefaultConfig = { + enabled: false, + timeout: 1000, + maxBids: 1, + adapter: 'prebidServer' +}; + +config.setDefaults({ + 's2sConfig': s2sDefaultConfig +}); + +// accountId and bidders params are not included here, should be configured by end-user +const availVendorDefaults = { + 'appnexus': { + adapter: 'prebidServer', + cookieSet: false, + enabled: true, + endpoint: '//prebid.adnxs.com/pbs/v1/auction', + syncEndpoint: '//prebid.adnxs.com/pbs/v1/cookie_sync', + timeout: 1000 + }, + 'rubicon': { + adapter: 'prebidServer', + cookieSet: false, + enabled: true, + endpoint: '//prebid-server.rubiconproject.com/auction', + syncEndpoint: '//prebid-server.rubiconproject.com/cookie_sync', + timeout: 500 + } +}; + +/** + * Set config for server to server header bidding + * @typedef {Object} options - required + * @property {boolean} enabled enables S2S bidding + * @property {string[]} bidders bidders to request S2S + * @property {string} endpoint endpoint to contact + * === optional params below === + * @property {number} [timeout] timeout for S2S bidders - should be lower than `pbjs.requestBids({timeout})` + * @property {boolean} [cacheMarkup] whether to cache the adm result + * @property {string} [adapter] adapter code to use for S2S + * @property {string} [syncEndpoint] endpoint URL for syncing cookies + * @property {string} [cookieSetUrl] url for cookie set library, if passed then cookieSet is enabled + */ +function setS2sConfig(options) { + if (options.defaultVendor) { + let vendor = options.defaultVendor; + let optionKeys = Object.keys(options); + + if (availVendorDefaults.hasOwnProperty(vendor)) { + // vendor keys will be set if either: the key was not specified by user + // or if the user did not set their own distinct value (ie using the system default) to override the vendor + Object.keys(availVendorDefaults[vendor]).forEach(function(vendorKey) { + if (s2sDefaultConfig[vendorKey] === options[vendorKey] || !includes(optionKeys, vendorKey)) { + options[vendorKey] = availVendorDefaults[vendor][vendorKey]; + } + }); + } else { + utils.logError('Incorrect or unavailable prebid server default vendor option: ' + vendor); + return false; + } + } + + let keys = Object.keys(options); + + if (['accountId', 'bidders', 'endpoint'].filter(key => { + if (!includes(keys, key)) { + utils.logError(key + ' missing in server to server config'); + return true; + } + return false; + }).length > 0) { + return; + } + + _s2sConfig = options; + if (options.syncEndpoint) { + queueSync(options.bidders); + } +} +getConfig('s2sConfig', ({s2sConfig}) => setS2sConfig(s2sConfig)); + +/** + * @param {Array} bidderCodes list of bidders to request user syncs for. + */ +function queueSync(bidderCodes) { + if (_synced) { + return; + } + _synced = true; + const payload = JSON.stringify({ + uuid: utils.generateUUID(), + bidders: bidderCodes + }); + ajax(_s2sConfig.syncEndpoint, (response) => { + try { + response = JSON.parse(response); + response.bidder_status.forEach(bidder => doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder)); + } catch (e) { + utils.logError(e); + } + }, + payload, { + contentType: 'text/plain', + withCredentials: true + }); +} + +/** + * Run a cookie sync for the given type, url, and bidder + * + * @param {string} type the type of sync, "image", "redirect", "iframe" + * @param {string} url the url to sync + * @param {string} bidder name of bidder doing sync for + */ +function doBidderSync(type, url, bidder) { + if (!url) { + utils.logError(`No sync url for bidder "${bidder}": ${url}`); + } else if (type === 'image' || type === 'redirect') { + utils.logMessage(`Invoking image pixel user sync for bidder: "${bidder}"`); + utils.triggerPixel(url); + } else if (type == 'iframe') { + utils.logMessage(`Invoking iframe user sync for bidder: "${bidder}"`); + utils.insertUserSyncIframe(url); + } else { + utils.logError(`User sync type "${type}" not supported for bidder: "${bidder}"`); + } +} + +/** + * Do client-side syncs for bidders. + * + * @param {Array} bidders a list of bidder names + */ +function doClientSideSyncs(bidders) { + bidders.forEach(bidder => { + let clientAdapter = adaptermanager.getBidAdapter(bidder); + if (clientAdapter && clientAdapter.registerSyncs) { + clientAdapter.registerSyncs([]); + } + }); +} /** * Try to convert a value to a type. @@ -65,210 +214,505 @@ const paramTypes = { 'cp': tryConvertNumber, 'ct': tryConvertNumber }, + 'conversant': { + 'site_id': tryConvertString, + 'secure': tryConvertNumber, + 'mobile': tryConvertNumber + }, + 'openx': { + 'unit': tryConvertString, + 'customFloor': tryConvertNumber + }, }; -let _cookiesQueued = false; - -/** - * Bidder adapter for Prebid Server +/* + * Modify an adunit's bidder parameters to match the expected parameter types */ -function PrebidServer() { - let baseAdapter = new Adapter('prebidServer'); - let config; +function convertTypes(adUnits) { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + // aliases use the base bidder's paramTypes + const bidder = adaptermanager.aliasRegistry[bid.bidder] || bid.bidder; + const types = paramTypes[bidder] || []; - baseAdapter.setConfig = function(s2sconfig) { - config = s2sconfig; - }; + Object.keys(types).forEach(key => { + if (bid.params[key]) { + bid.params[key] = types[key](bid.params[key]); - function convertTypes(adUnits) { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const types = paramTypes[bid.bidder] || []; - Object.keys(types).forEach(key => { - if (bid.params[key]) { - const converted = types[key](bid.params[key]); - if (converted !== bid.params[key]) { - utils.logMessage(`Mismatched type for Prebid Server : ${bid.bidder} : ${key}. Required Type:${types[key]}`); - } - bid.params[key] = converted; - - // don't send invalid values - if (isNaN(bid.params[key])) { - delete bid.params.key; - } + // don't send invalid values + if (isNaN(bid.params[key])) { + delete bid.params.key; } - }); + } }); }); + }); +} + +function _getDigiTrustQueryParams() { + function getDigiTrustId() { + let digiTrustUser = window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'})); + return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; + } + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { + return null; } + return { + id: digiTrustId.id, + keyv: digiTrustId.keyv, + pref: 0 + }; +} - /* Prebid executes this function when the page asks to send out bid requests */ - baseAdapter.callBids = function(bidRequest) { - const isDebug = !!getConfig('debug'); - const adUnits = utils.cloneJson(bidRequest.ad_units); +function _appendSiteAppDevice(request) { + if (!request) return; + + // ORTB specifies app OR site + if (typeof config.getConfig('app') === 'object') { + request.app = config.getConfig('app'); + request.app.publisher = {id: _s2sConfig.accountId} + } else { + request.site = { + publisher: { id: _s2sConfig.accountId }, + page: utils.getTopWindowUrl() + } + } + if (typeof config.getConfig('device') === 'object') { + request.device = config.getConfig('device'); + } +} + +function transformHeightWidth(adUnit) { + let sizesObj = []; + let sizes = utils.parseSizesInput(adUnit.sizes); + sizes.forEach(size => { + let heightWidth = size.split('x'); + let sizeObj = { + 'w': parseInt(heightWidth[0]), + 'h': parseInt(heightWidth[1]) + }; + sizesObj.push(sizeObj); + }); + return sizesObj; +} + +/* + * Protocol spec for legacy endpoint + * e.g., https:///v1/auction + */ +const LEGACY_PROTOCOL = { + + buildRequest(s2sBidRequest, bidRequests, adUnits) { + // pbs expects an ad_unit.video attribute if the imp is video adUnits.forEach(adUnit => { - let videoMediaType = utils.deepAccess(adUnit, 'mediaTypes.video'); + adUnit.sizes = transformHeightWidth(adUnit); + const videoMediaType = utils.deepAccess(adUnit, 'mediaTypes.video'); if (videoMediaType) { - // pbs expects a ad_unit.video attribute if the imp is video adUnit.video = Object.assign({}, videoMediaType); - delete adUnit.mediaTypes.video; + delete adUnit.mediaTypes; + // default is assumed to be 'banner' so if there is a video type + // we assume video only until PBS can support multi-format auction + adUnit.media_types = [VIDEO]; } - }) - convertTypes(adUnits); - let requestJson = { - account_id: config.accountId, - tid: bidRequest.tid, - max_bids: config.maxBids, - timeout_millis: config.timeout, - secure: config.secure, + }); + + const request = { + account_id: _s2sConfig.accountId, + tid: s2sBidRequest.tid, + max_bids: _s2sConfig.maxBids, + timeout_millis: _s2sConfig.timeout, + secure: _s2sConfig.secure, + cache_markup: _s2sConfig.cacheMarkup === 1 || _s2sConfig.cacheMarkup === 2 ? _s2sConfig.cacheMarkup : 0, url: utils.getTopWindowUrl(), prebid_version: '$prebid.version$', - ad_units: adUnits.filter(hasSizes), - is_debug: isDebug + ad_units: adUnits, + is_debug: !!getConfig('debug'), }; - // in case config.bidders contains invalid bidders, we only process those we sent requests for. - const requestedBidders = requestJson.ad_units.map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(utils.uniques)).reduce(utils.flatten).filter(utils.uniques); - function processResponse(response) { - handleResponse(response, requestedBidders); + _appendSiteAppDevice(request); + + let digiTrust = _getDigiTrustQueryParams(); + if (digiTrust) { + request.digiTrust = digiTrust; } - const payload = JSON.stringify(requestJson); - ajax(config.endpoint, processResponse, payload, { - contentType: 'text/plain', - withCredentials: true - }); - }; - // at this point ad units should have a size array either directly or mapped so filter for that - function hasSizes(unit) { - return unit.sizes && unit.sizes.length; - } + return request; + }, - /** - * Run a cookie sync for the given type, url, and bidder - * - * @param {string} type the type of sync, "image", "redirect", "iframe" - * @param {string} url the url to sync - * @param {string} bidder name of bidder doing sync for - */ - function doBidderSync(type, url, bidder) { - if (!url) { - utils.logError(`No sync url for bidder "${bidder}": ${url}`); - } else if (type === 'image' || type === 'redirect') { - utils.logMessage(`Invoking image pixel user sync for bidder: "${bidder}"`); - utils.triggerPixel(url); - } else if (type == 'iframe') { - utils.logMessage(`Invoking iframe user sync for bidder: "${bidder}"`); - utils.insertUserSyncIframe(url); - } else { - utils.logError(`User sync type "${type}" not supported for bidder: "${bidder}"`); + interpretResponse(result, bidRequests, requestedBidders) { + const bids = []; + let responseTimes = {}; + + if (result.status === 'OK' || result.status === 'no_cookie') { + if (result.bidder_status) { + result.bidder_status.forEach(bidder => { + if (bidder.no_cookie) { + doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder); + } + if (bidder.error) { + utils.logWarn(`Prebid Server returned error: '${bidder.error}' for ${bidder.bidder}`); + } + + responseTimes[bidder.bidder] = bidder.response_time_ms; + }); + } + + if (result.bids) { + result.bids.forEach(bidObj => { + const bidRequest = utils.getBidRequest(bidObj.bid_id, bidRequests); + const cpm = bidObj.price; + const status = cpm !== 0 ? STATUS.GOOD : STATUS.NO_BID; + let bidObject = bidfactory.createBid(status, bidRequest); + + bidObject.source = TYPE; + bidObject.creative_id = bidObj.creative_id; + bidObject.bidderCode = bidObj.bidder; + bidObject.cpm = cpm; + if (responseTimes[bidObj.bidder]) { + bidObject.serverResponseTimeMs = responseTimes[bidObj.bidder]; + } + if (bidObj.cache_id) { + bidObject.cache_id = bidObj.cache_id; + } + if (bidObj.cache_url) { + bidObject.cache_url = bidObj.cache_url; + } + // From ORTB see section 4.2.3: adm Optional means of conveying ad markup in case the bid wins; supersedes the win notice if markup is included in both. + if (bidObj.media_type === VIDEO) { + bidObject.mediaType = VIDEO; + if (bidObj.adm) { + bidObject.vastXml = bidObj.adm; + } + if (bidObj.nurl) { + bidObject.vastUrl = bidObj.nurl; + } + // when video bid is already cached by Prebid Server, videoCacheKey and vastUrl should be provided properly + if (bidObj.cache_id && bidObj.cache_url) { + bidObject.videoCacheKey = bidObj.cache_id; + bidObject.vastUrl = bidObj.cache_url; + } + } else { + if (bidObj.adm && bidObj.nurl) { + bidObject.ad = bidObj.adm; + bidObject.ad += utils.createTrackPixelHtml(decodeURIComponent(bidObj.nurl)); + } else if (bidObj.adm) { + bidObject.ad = bidObj.adm; + } else if (bidObj.nurl) { + bidObject.adUrl = bidObj.nurl; + } + } + + bidObject.width = bidObj.width; + bidObject.height = bidObj.height; + bidObject.adserverTargeting = bidObj.ad_server_targeting; + if (bidObj.deal_id) { + bidObject.dealId = bidObj.deal_id; + } + bidObject.requestId = bidObj.bid_id; + bidObject.creativeId = bidObj.creative_id; + + // TODO: Remove when prebid-server returns ttl, currency and netRevenue + bidObject.ttl = (bidObj.ttl) ? bidObj.ttl : DEFAULT_S2S_TTL; + bidObject.currency = (bidObj.currency) ? bidObj.currency : DEFAULT_S2S_CURRENCY; + bidObject.netRevenue = (bidObj.netRevenue) ? bidObj.netRevenue : DEFAULT_S2S_NETREVENUE; + + if (result.burl) { bidObject.burl = result.burl; } + + bids.push({ adUnit: bidObj.code, bid: bidObject }); + }); + } } + + return bids; } +}; - /* Notify Prebid of bid responses so bids can get in the auction */ - function handleResponse(response, requestedBidders) { - let result; - try { - result = JSON.parse(response); +/* + * Protocol spec for OpenRTB endpoint + * e.g., https:///v1/openrtb2/auction + */ +const OPEN_RTB_PROTOCOL = { + + bidMap: {}, + + buildRequest(s2sBidRequest, bidRequests, adUnits) { + let imps = []; + let aliases = {}; + + // transform ad unit into array of OpenRTB impression objects + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + // OpenRTB response contains the adunit code and bidder name. These are + // combined to create a unique key for each bid since an id isn't returned + const key = `${adUnit.code}${bid.bidder}`; + this.bidMap[key] = bid; + + // check for and store valid aliases to add to the request + if (adaptermanager.aliasRegistry[bid.bidder]) { + aliases[bid.bidder] = adaptermanager.aliasRegistry[bid.bidder]; + } + }); + + let banner; + // default to banner if mediaTypes isn't defined + if (utils.isEmpty(adUnit.mediaTypes)) { + const sizeObjects = adUnit.sizes.map(size => ({ w: size[0], h: size[1] })); + banner = {format: sizeObjects}; + } + + const bannerParams = utils.deepAccess(adUnit, 'mediaTypes.banner'); + if (bannerParams && bannerParams.sizes) { + const sizes = utils.parseSizesInput(bannerParams.sizes); + + // get banner sizes in form [{ w: , h: }, ...] + const format = sizes.map(size => { + const [ width, height ] = size.split('x'); + const w = parseInt(width, 10); + const h = parseInt(height, 10); + return { w, h }; + }); + + banner = {format}; + } - if (result.status === 'OK' || result.status === 'no_cookie') { - if (result.bidder_status) { - result.bidder_status.forEach(bidder => { - if (bidder.no_cookie && !_cookiesQueued) { - doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder); + let video; + const videoParams = utils.deepAccess(adUnit, 'mediaTypes.video'); + if (!utils.isEmpty(videoParams)) { + video = videoParams; + } + + // get bidder params in form { : {...params} } + const ext = adUnit.bids.reduce((acc, bid) => { + // TODO: move this bidder specific out to a more ideal location (submodule?); issue# pending + // convert all AppNexus keys to underscore format for pbs + if (bid.bidder === 'appnexus') { + bid.params.use_pmt_rule = (typeof bid.params.usePaymentRule === 'boolean') ? bid.params.usePaymentRule : false; + if (bid.params.usePaymentRule) { delete bid.params.usePaymentRule; } + + Object.keys(bid.params).forEach(paramKey => { + let convertedKey = utils.convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + bid.params[convertedKey] = bid.params[paramKey]; + delete bid.params[paramKey]; } }); } + acc[bid.bidder] = bid.params; + return acc; + }, {}); - if (result.bids) { - result.bids.forEach(bidObj => { - let bidRequest = utils.getBidRequest(bidObj.bid_id); - let cpm = bidObj.price; - let status; - if (cpm !== 0) { - status = STATUS.GOOD; - } else { - status = STATUS.NO_BID; - } + const imp = { id: adUnit.code, ext, secure: _s2sConfig.secure }; - let bidObject = bidfactory.createBid(status, bidRequest); - bidObject.source = TYPE; - bidObject.creative_id = bidObj.creative_id; - bidObject.bidderCode = bidObj.bidder; - bidObject.cpm = cpm; - bidObject.ad = bidObj.adm; - if (bidObj.nurl) { - bidObject.ad += utils.createTrackPixelHtml(decodeURIComponent(bidObj.nurl)); - } - bidObject.width = bidObj.width; - bidObject.height = bidObj.height; - bidObject.adserverTargeting = bidObj.ad_server_targeting; - if (bidObj.deal_id) { - bidObject.dealId = bidObj.deal_id; - } + if (banner) { imp.banner = banner; } + if (video) { imp.video = video; } - bidmanager.addBidResponse(bidObj.code, bidObject); - }); + imps.push(imp); + }); + + const request = { + id: s2sBidRequest.tid, + source: {tid: s2sBidRequest.tid}, + tmax: _s2sConfig.timeout, + imp: imps, + test: getConfig('debug') ? 1 : 0, + }; + + _appendSiteAppDevice(request); + + const digiTrust = _getDigiTrustQueryParams(); + if (digiTrust) { + request.user = { ext: { digitrust: digiTrust } }; + } + + if (!utils.isEmpty(aliases)) { + request.ext = { prebid: { aliases } }; + } + + if (bidRequests && bidRequests[0].gdprConsent) { + // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module + let gdprApplies; + if (typeof bidRequests[0].gdprConsent.gdprApplies === 'boolean') { + gdprApplies = bidRequests[0].gdprConsent.gdprApplies ? 1 : 0; + } + + if (request.regs) { + if (request.regs.ext) { + request.regs.ext.gdpr = gdprApplies; + } else { + request.regs.ext = { gdpr: gdprApplies }; } + } else { + request.regs = { ext: { gdpr: gdprApplies } }; + } - const receivedBidIds = result.bids ? result.bids.map(bidObj => bidObj.bid_id) : []; - - // issue a no-bid response for every bid request that can not be matched with received bids - requestedBidders.forEach(bidder => { - utils - .getBidderRequestAllAdUnits(bidder) - .bids.filter(bidRequest => !receivedBidIds.includes(bidRequest.bidId)) - .forEach(bidRequest => { - let bidObject = bidfactory.createBid(STATUS.NO_BID, bidRequest); - bidObject.source = TYPE; - bidObject.adUnitCode = bidRequest.placementCode; - bidObject.bidderCode = bidRequest.bidder; - - bidmanager.addBidResponse(bidObject.adUnitCode, bidObject); - }); - }); + let consentString = bidRequests[0].gdprConsent.consentString; + if (request.user) { + if (request.user.ext) { + request.user.ext.consent = consentString; + } else { + request.user.ext = { consent: consentString }; + } + } else { + request.user = { ext: { consent: consentString } }; } - if (result.status === 'no_cookie' && config.cookieSet) { + } + + return request; + }, + + interpretResponse(response, bidRequests, requestedBidders) { + const bids = []; + + if (response.seatbid) { + // a seatbid object contains a `bid` array and a `seat` string + response.seatbid.forEach(seatbid => { + (seatbid.bid || []).forEach(bid => { + const bidRequest = utils.getBidRequest( + this.bidMap[`${bid.impid}${seatbid.seat}`], + bidRequests + ); + + const cpm = bid.price; + const status = cpm !== 0 ? STATUS.GOOD : STATUS.NO_BID; + let bidObject = bidfactory.createBid(status, bidRequest); + + bidObject.source = TYPE; + bidObject.bidderCode = seatbid.seat; + bidObject.cpm = cpm; + + let serverResponseTimeMs = utils.deepAccess(response, ['ext', 'responsetimemillis', seatbid.seat].join('.')); + if (serverResponseTimeMs) { + bidObject.serverResponseTimeMs = serverResponseTimeMs; + } + + if (utils.deepAccess(bid, 'ext.prebid.type') === VIDEO) { + bidObject.mediaType = VIDEO; + if (bid.adm) { bidObject.vastXml = bid.adm; } + if (bid.nurl) { bidObject.vastUrl = bid.nurl; } + } else { // banner + if (bid.adm && bid.nurl) { + bidObject.ad = bid.adm; + bidObject.ad += utils.createTrackPixelHtml(decodeURIComponent(bid.nurl)); + } else if (bid.adm) { + bidObject.ad = bid.adm; + } else if (bid.nurl) { + bidObject.adUrl = bid.nurl; + } + } + + bidObject.width = bid.w; + bidObject.height = bid.h; + if (bid.dealid) { bidObject.dealId = bid.dealid; } + bidObject.requestId = bid.id; + bidObject.creative_id = bid.crid; + bidObject.creativeId = bid.crid; + if (bid.burl) { bidObject.burl = bid.burl; } + + // TODO: Remove when prebid-server returns ttl, currency and netRevenue + bidObject.ttl = (bid.ttl) ? bid.ttl : DEFAULT_S2S_TTL; + bidObject.currency = (bid.currency) ? bid.currency : DEFAULT_S2S_CURRENCY; + bidObject.netRevenue = (bid.netRevenue) ? bid.netRevenue : DEFAULT_S2S_NETREVENUE; + + bids.push({ adUnit: bid.impid, bid: bidObject }); + }); + }); + } + + return bids; + } +}; + +/* + * Returns the required protocol adapter to communicate with the configured + * endpoint. The adapter is an object containing `buildRequest` and + * `interpretResponse` functions. + * + * Usage: + * // build JSON payload to send to server + * const request = protocol().buildRequest(s2sBidRequest, adUnits); + * + * // turn server response into bid object array + * const bids = protocol().interpretResponse(response, bidRequests, requestedBidders); + */ +const protocolAdapter = () => { + const OPEN_RTB_PATH = 'openrtb2/auction'; + + const endpoint = (_s2sConfig && _s2sConfig.endpoint) || ''; + const isOpenRtb = ~endpoint.indexOf(OPEN_RTB_PATH); + + return isOpenRtb ? OPEN_RTB_PROTOCOL : LEGACY_PROTOCOL; +}; + +/** + * Bidder adapter for Prebid Server + */ +export function PrebidServer() { + const baseAdapter = new Adapter('prebidServer'); + + /* Prebid executes this function when the page asks to send out bid requests */ + baseAdapter.callBids = function(s2sBidRequest, bidRequests, addBidResponse, done, ajax) { + const adUnits = utils.deepClone(s2sBidRequest.ad_units); + + convertTypes(adUnits); + + // at this point ad units should have a size array either directly or mapped so filter for that + const adUnitsWithSizes = adUnits.filter(unit => unit.sizes && unit.sizes.length); + + // in case config.bidders contains invalid bidders, we only process those we sent requests for + const requestedBidders = adUnitsWithSizes + .map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(utils.uniques)) + .reduce(utils.flatten) + .filter(utils.uniques); + + const request = protocolAdapter().buildRequest(s2sBidRequest, bidRequests, adUnitsWithSizes); + const requestJson = JSON.stringify(request); + + ajax( + _s2sConfig.endpoint, + response => handleResponse(response, requestedBidders, bidRequests, addBidResponse, done), + requestJson, + { contentType: 'text/plain', withCredentials: true } + ); + }; + + /* Notify Prebid of bid responses so bids can get in the auction */ + function handleResponse(response, requestedBidders, bidRequests, addBidResponse, done) { + let result; + + try { + result = JSON.parse(response); + + const bids = protocolAdapter().interpretResponse( + result, + bidRequests, + requestedBidders + ); + + bids.forEach(({adUnit, bid}) => { + if (isValid(adUnit, bid, bidRequests)) { + addBidResponse(adUnit, bid); + } + }); + + if (result.status === 'no_cookie' && _s2sConfig.cookieSet && typeof _s2sConfig.cookieSetUrl === 'string') { // cookie sync - cookieSet(cookieSetUrl); + cookieSet(_s2sConfig.cookieSetUrl); } } catch (error) { utils.logError(error); } - if (!result || (result.status && result.status.includes('Error'))) { + if (!result || (result.status && includes(result.status, 'Error'))) { utils.logError('error parsing response: ', result.status); } + + done(); + doClientSideSyncs(requestedBidders); } - /** - * @param {} {bidders} list of bidders to request user syncs for. - */ - baseAdapter.queueSync = function({bidderCodes}) { - if (_synced) { - return; - } - _synced = true; - const payload = JSON.stringify({ - uuid: utils.generateUUID(), - bidders: bidderCodes - }); - ajax(config.syncEndpoint, (response) => { - try { - response = JSON.parse(response); - response.bidder_status.forEach(bidder => doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder)); - } catch (e) { - utils.logError(e); - } - }, - payload, { - contentType: 'text/plain', - withCredentials: true - }); - }; return Object.assign(this, { - queueSync: baseAdapter.queueSync, - setConfig: baseAdapter.setConfig, callBids: baseAdapter.callBids, setBidderCode: baseAdapter.setBidderCode, type: TYPE @@ -276,5 +720,3 @@ function PrebidServer() { } adaptermanager.registerBidAdapter(new PrebidServer(), 'prebidServer'); - -module.exports = PrebidServer; diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js new file mode 100644 index 00000000000..58b91ae956c --- /dev/null +++ b/modules/pubCommonId.js @@ -0,0 +1,100 @@ +/** + * This modules adds Publisher Common ID support to prebid.js. It's a simple numeric id + * stored in the page's domain. When the module is included, an id is generated if needed, + * persisted as a cookie, and automatically appended to all the bidRequest as bid.crumbs.pubcid. + */ +import * as utils from 'src/utils' +import { config } from 'src/config'; + +const COOKIE_NAME = '_pubcid'; +const DEFAULT_EXPIRES = 2628000; // 5-year worth of minutes +const PUB_COMMON = 'PublisherCommonId'; + +var pubcidEnabled = true; +var interval = DEFAULT_EXPIRES; + +export function isPubcidEnabled() { return pubcidEnabled; } +export function getExpInterval() { return interval; } + +/** + * Decorate ad units with pubcid. This hook function is called before the + * real pbjs.requestBids is invoked, and can modify its parameter. The cookie is + * not updated until this function is called. + * @param {Object} config This is the same parameter as pbjs.requestBids, and config.adUnits will be updated. + * @param {function} next The next function in the chain + */ + +export function requestBidHook(config, next) { + let adUnits = config.adUnits || $$PREBID_GLOBAL$$.adUnits; + let pubcid = null; + + // Pass control to the next function if not enabled + if (!pubcidEnabled) { + return next.apply(this, arguments); + } + + if (typeof window[PUB_COMMON] === 'object') { + // If the page includes its own pubcid object, then use that instead. + pubcid = window[PUB_COMMON].getId(); + utils.logMessage(PUB_COMMON + ': pubcid = ' + pubcid); + } else { + // Otherwise get the existing cookie or create a new id + pubcid = getCookie(COOKIE_NAME) || utils.generateUUID(); + + // Update the cookie with the latest expiration date + setCookie(COOKIE_NAME, pubcid, interval); + utils.logMessage('pbjs: pubcid = ' + pubcid); + } + + // Append pubcid to each bid object, which will be incorporated + // into bid requests later. + if (adUnits && pubcid) { + adUnits.forEach((unit) => { + unit.bids.forEach((bid) => { + Object.assign(bid, {crumbs: {pubcid}}); + }); + }); + } + return next.apply(this, arguments); +} + +// Helper to set a cookie +export function setCookie(name, value, expires) { + let expTime = new Date(); + expTime.setTime(expTime.getTime() + expires * 1000 * 60); + window.document.cookie = name + '=' + encodeURIComponent(value) + ';path=/;expires=' + + expTime.toGMTString(); +} + +// Helper to read a cookie +export function getCookie(name) { + let m = window.document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]*)\\s*(;|$)'); + return m ? decodeURIComponent(m[2]) : null; +} + +/** + * Configuration function + * @param {boolean} enable Enable or disable pubcid. By default the module is enabled. + * @param {number} expInterval Expiration interval of the cookie in minutes. + */ + +export function setConfig({ enable = true, expInterval = DEFAULT_EXPIRES } = {}) { + pubcidEnabled = enable; + interval = parseInt(expInterval, 10); + if (isNaN(interval)) { + interval = DEFAULT_EXPIRES; + } +} + +/** + * Initialize module by 1) subscribe to configuration changes and 2) register hook + */ +export function initPubcid() { + config.getConfig('pubcid', config => setConfig(config.pubcid)); + + if (utils.cookiesAreEnabled()) { + $$PREBID_GLOBAL$$.requestBids.addHook(requestBidHook); + } +} + +initPubcid(); diff --git a/modules/pubgearsBidAdapter.js b/modules/pubgearsBidAdapter.js deleted file mode 100644 index fd6ccee453a..00000000000 --- a/modules/pubgearsBidAdapter.js +++ /dev/null @@ -1,150 +0,0 @@ -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var consts = require('src/constants.json'); -var utils = require('src/utils.js'); -var adaptermanager = require('src/adaptermanager'); -var d = document; -var SCRIPT = 'script'; -var PARAMS = 'params'; -var SIZES = 'sizes'; -var SIZE = 'size'; -var CPM = 'cpm'; -var AD = 'ad'; -var WIDTH = 'width'; -var HEIGHT = 'height'; -var PUB_ZONE = 'pub_zone'; -var GROSS_PRICE = 'gross_price'; -var RESOURCE = 'resource'; -var DETAIL = 'detail'; -var BIDDER_CODE_RESPONSE_KEY = 'bidderCode'; -var BIDDER_CODE = 'pubgears'; -var SCRIPT_ID = 'pg-header-tag'; -var ATTRIBUTE_PREFIX = 'data-bsm-'; -var SLOT_LIST_ATTRIBUTE = 'slot-list'; -var PUBLISHER_ATTRIBUTE = 'pub'; -var FLAG_ATTRIBUTE = 'flag'; -var PLACEMENT_CODE = 'placementCode'; -var BID_ID = 'bidId'; -var PUBLISHER_PARAM = 'publisherName'; -var PUB_ZONE_PARAM = 'pubZone'; -var BID_RECEIVED_EVENT_NAME = 'onBidResponse'; -var SLOT_READY_EVENT_NAME = 'onResourceComplete'; -var CREATIVE_TEMPLATE = decodeURIComponent("%3Cscript%3E%0A(function(define)%7B%0Adefine(function(a)%7B%0A%09var%20id%3D%20%22pg-ad-%22%20%2B%20Math.floor(Math.random()%20*%201e10)%2C%20d%3D%20document%0A%09d.write(\'%3Cdiv%20id%3D%22\'%2Bid%2B\'%22%3E%3C%2Fdiv%3E\')%0A%09a.push(%7B%0A%09%09pub%3A%20\'%25%25PUBLISHER_NAME%25%25\'%2C%0A%09%09pub_zone%3A%20\'%25%25PUB_ZONE%25%25\'%2C%0A%09%09sizes%3A%20%5B\'%25%25SIZE%25%25\'%5D%2C%0A%09%09flag%3A%20true%2C%0A%09%09container%3A%20d.getElementById(id)%2C%0A%09%7D)%3B%0A%7D)%7D)(function(f)%7Bvar%20key%3D\'uber_imps\'%2Ca%3Dthis%5Bkey%5D%3Dthis%5Bkey%5D%7C%7C%5B%5D%3Bf(a)%3B%7D)%3B%0A%3C%2Fscript%3E%0A%3Cscript%20src%3D%22%2F%2Fc.pubgears.com%2Ftags%2Fb%22%3E%3C%2Fscript%3E%0A"); -var TAG_URL = '//c.pubgears.com/tags/h'; -var publisher = ''; - -adaptermanager.registerBidAdapter(new PubGearsAdapter(), BIDDER_CODE); - -module.exports = PubGearsAdapter; - -function PubGearsAdapter() { - var proxy = null; - var pendingSlots = {}; - var initialized = false; - - this.callBids = callBids; - - function callBids(params) { - var bids = params[consts.JSON_MAPPING.PL_BIDS]; - var slots = bids.map(getSlotFromBidParam); - if (slots.length <= 0) { return; } - publisher = bids[0][PARAMS][PUBLISHER_PARAM]; - - bids.forEach(function(bid) { - var name = getSlotFromBidParam(bid); - pendingSlots[ name ] = bid; - }); - - proxy = proxy || getScript(SCRIPT_ID) || makeScript(slots, publisher, SCRIPT_ID, TAG_URL); - if (!initialized) { registerEventListeners(proxy); } - initialized = true; - } - function loadScript(script) { - var anchor = (function(scripts) { - return scripts[ scripts.length - 1 ]; - })(d.getElementsByTagName(SCRIPT)); - - return anchor.parentNode.insertBefore(script, anchor); - } - function getSlotFromBidParam(bid) { - var size = getSize(bid); - var params = bid[PARAMS]; - var slotName = params[PUB_ZONE_PARAM]; - return [ slotName, size ].join('@'); - } - function getSlotFromResource(resource) { - var size = resource[SIZE]; - var key = [ resource[PUB_ZONE], size ].join('@'); - return key; - } - function getSize(bid) { - var sizes = bid[SIZES]; - var size = Array.isArray(sizes[0]) ? sizes[0] : sizes; - return size.join('x'); - } - function makeScript(slots, publisher, id, url) { - var script = d.createElement(SCRIPT); - script.src = url; - script.id = id; - script.setAttribute(ATTRIBUTE_PREFIX + SLOT_LIST_ATTRIBUTE, slots.join(' ')); - script.setAttribute(ATTRIBUTE_PREFIX + FLAG_ATTRIBUTE, 'true'); - script.setAttribute(ATTRIBUTE_PREFIX + PUBLISHER_ATTRIBUTE, publisher); - - return loadScript(script); - } - function getScript(id) { - return d.getElementById(id); - } - function registerEventListeners(script) { - script.addEventListener(BID_RECEIVED_EVENT_NAME, onBid, true); - script.addEventListener(SLOT_READY_EVENT_NAME, onComplete, true); - } - function onBid(event) { - var data = event[DETAIL]; - var slotKey = getSlotFromResource(data[RESOURCE]); - var bidRequest = pendingSlots[slotKey]; - var adUnitCode = bidRequest[PLACEMENT_CODE]; - var bid = null; - - if (bidRequest) { - bid = buildResponse(data, bidRequest); - bidmanager.addBidResponse(adUnitCode, bid); - utils.logMessage('adding bid respoonse to "' + adUnitCode + '" for bid request "' + bidRequest[BID_ID] + '"'); - } else { - utils.logError('Cannot get placement id for slot "' + slotKey + '"'); - } - } - function buildResponse(eventData, bidRequest) { - var resource = eventData[RESOURCE]; - var dims = resource[SIZE].split('x'); - var price = Number(eventData[GROSS_PRICE]); - var status = isNaN(price) || price <= 0 ? 2 : 1; - - var response = bidfactory.createBid(status, bidRequest); - response[BIDDER_CODE_RESPONSE_KEY] = BIDDER_CODE; - - if (status !== 1) { return response; } - - response[AD] = getCreative(resource); - - response[CPM] = price / 1e3; - response[WIDTH] = dims[0]; - response[HEIGHT] = dims[1]; - return response; - } - function getCreative(resource) { - var token = '%%'; - var creative = CREATIVE_TEMPLATE; - var replacementValues = { - publisher_name: publisher, - pub_zone: resource[PUB_ZONE], - size: resource[SIZE] - }; - return utils.replaceTokenInString(creative, replacementValues, token); - } - function onComplete(event) { - var data = event[DETAIL]; - var slotKey = getSlotFromResource(data[RESOURCE]); - delete pendingSlots[slotKey]; - } -} diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 1517a53eee1..14da2c45164 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,154 +1,323 @@ -var utils = require('src/utils.js'); -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adaptermanager = require('src/adaptermanager'); - -/** - * Adapter for requesting bids from Pubmatic. - * - * @returns {{callBids: _callBids}} - * @constructor - */ -function PubmaticAdapter() { - var bids; - var _pm_pub_id; - var _pm_pub_age; - var _pm_pub_gender; - var _pm_pub_kvs; - var _pm_optimize_adslots = []; - let iframe; - - function _callBids(params) { - bids = params.bids; - _pm_optimize_adslots = []; - for (var i = 0; i < bids.length; i++) { - var bid = bids[i]; - // bidmanager.pbCallbackMap['' + bid.params.adSlot] = bid; - _pm_pub_id = _pm_pub_id || bid.params.publisherId; - _pm_pub_age = _pm_pub_age || (bid.params.age || ''); - _pm_pub_gender = _pm_pub_gender || (bid.params.gender || ''); - _pm_pub_kvs = _pm_pub_kvs || (bid.params.kvs || ''); - _pm_optimize_adslots.push(bid.params.adSlot); - } +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +const constants = require('src/constants.json'); - // Load pubmatic script in an iframe, because they call document.write - _getBids(); - } +const BIDDER_CODE = 'pubmatic'; +const ENDPOINT = '//hbopenbid.pubmatic.com/translator?source=prebid-client'; +const USYNCURL = '//ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p='; +const CURRENCY = 'USD'; +const AUCTION_TYPE = 1; +const UNDEFINED = undefined; +const CUSTOM_PARAMS = { + 'kadpageurl': '', // Custom page url + 'gender': '', // User gender + 'yob': '', // User year of birth + 'lat': '', // User location - Latitude + 'lon': '', // User Location - Longitude + 'wiid': '', // OpenWrap Wrapper Impression ID + 'profId': '', // OpenWrap Legacy: Profile ID + 'verId': '' // OpenWrap Legacy: version ID +}; +const NET_REVENUE = false; +const dealChannelValues = { + 1: 'PMP', + 5: 'PREF', + 6: 'PMPG' +}; - function _getBids() { - // create the iframe - iframe = utils.createInvisibleIframe(); +let publisherId = 0; - var elToAppend = document.getElementsByTagName('head')[0]; +function _getDomainFromURL(url) { + let anchor = document.createElement('a'); + anchor.href = url; + return anchor.hostname; +} - // insert the iframe into document - elToAppend.insertBefore(iframe, elToAppend.firstChild); +function _parseSlotParam(paramName, paramValue) { + if (!utils.isStr(paramValue)) { + paramValue && utils.logWarn('PubMatic: Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); + return UNDEFINED; + } - var iframeDoc = utils.getIframeDocument(iframe); - iframeDoc.write(_createRequestContent()); - iframeDoc.close(); + switch (paramName) { + case 'pmzoneid': + return paramValue.split(',').slice(0, 50).map(id => id.trim()).join(); + case 'kadfloor': + return parseFloat(paramValue) || UNDEFINED; + case 'lat': + return parseFloat(paramValue) || UNDEFINED; + case 'lon': + return parseFloat(paramValue) || UNDEFINED; + case 'yob': + return parseInt(paramValue) || UNDEFINED; + default: + return paramValue; } +} - function _createRequestContent() { - var content = 'inDapIF=true;'; - content += ''; - content += ''; - content += '' + - 'window.pm_pub_id = "%%PM_PUB_ID%%";' + - 'window.pm_optimize_adslots = [%%PM_OPTIMIZE_ADSLOTS%%];' + - 'window.kaddctr = "%%PM_ADDCTR%%";' + - 'window.kadgender = "%%PM_GENDER%%";' + - 'window.kadage = "%%PM_AGE%%";' + - 'window.pm_async_callback_fn = "window.parent.$$PREBID_GLOBAL$$.handlePubmaticCallback";'; - - content += ''; - - var map = {}; - map.PM_PUB_ID = _pm_pub_id; - map.PM_ADDCTR = _pm_pub_kvs; - map.PM_GENDER = _pm_pub_gender; - map.PM_AGE = _pm_pub_age; - map.PM_OPTIMIZE_ADSLOTS = _pm_optimize_adslots.map(function (adSlot) { - return "'" + adSlot + "'"; - }).join(','); - - content += ''; - content += ''; - content += ''; - content += ''; - content = utils.replaceTokenInString(content, map, '%%'); - - return content; +function _cleanSlot(slotName) { + if (utils.isStr(slotName)) { + return slotName.replace(/^\s+/g, '').replace(/\s+$/g, ''); } + return ''; +} - $$PREBID_GLOBAL$$.handlePubmaticCallback = function () { - let bidDetailsMap = {}; - let progKeyValueMap = {}; - try { - bidDetailsMap = iframe.contentWindow.bidDetailsMap; - progKeyValueMap = iframe.contentWindow.progKeyValueMap; - } catch (e) { - utils.logError(e, 'Error parsing Pubmatic response'); - } +function _parseAdSlot(bid) { + bid.params.adUnit = ''; + bid.params.adUnitIndex = '0'; + bid.params.width = 0; + bid.params.height = 0; + + bid.params.adSlot = _cleanSlot(bid.params.adSlot); - var i; - var adUnit; - var adUnitInfo; - var bid; - var bidResponseMap = bidDetailsMap || {}; - var bidInfoMap = progKeyValueMap || {}; - var dimensions; + var slot = bid.params.adSlot; + var splits = slot.split(':'); + + slot = splits[0]; + if (splits.length == 2) { + bid.params.adUnitIndex = splits[1]; + } + splits = slot.split('@'); + if (splits.length != 2) { + utils.logWarn('AdSlot Error: adSlot not in required format'); + return; + } + bid.params.adUnit = splits[0]; + splits = splits[1].split('x'); + if (splits.length != 2) { + utils.logWarn('AdSlot Error: adSlot not in required format'); + return; + } + bid.params.width = parseInt(splits[0]); + bid.params.height = parseInt(splits[1]); +} - for (i = 0; i < bids.length; i++) { - var adResponse; - bid = bids[i].params; +function _initConf() { + var conf = {}; + conf.pageURL = utils.getTopWindowUrl(); + conf.refURL = utils.getTopWindowReferrer(); + return conf; +} - adUnit = bidResponseMap[bid.adSlot] || {}; +function _handleCustomParams(params, conf) { + if (!conf.kadpageurl) { + conf.kadpageurl = conf.pageURL; + } - // adUnitInfo example: bidstatus=0;bid=0.0000;bidid=39620189@320x50;wdeal= + var key, value, entry; + for (key in CUSTOM_PARAMS) { + if (CUSTOM_PARAMS.hasOwnProperty(key)) { + value = params[key]; + if (value) { + entry = CUSTOM_PARAMS[key]; - // if using DFP GPT, the params string comes in the format: - // "bidstatus;1;bid;5.0000;bidid;hb_test@468x60;wdeal;" - // the code below detects and handles this. - if (bidInfoMap[bid.adSlot] && bidInfoMap[bid.adSlot].indexOf('=') === -1) { - bidInfoMap[bid.adSlot] = bidInfoMap[bid.adSlot].replace(/([a-z]+);(.[^;]*)/ig, '$1=$2'); - } + if (typeof entry === 'object') { + // will be used in future when we want to process a custom param before using + // 'keyname': {f: function() {}} + value = entry.f(value, conf); + } - adUnitInfo = (bidInfoMap[bid.adSlot] || '').split(';').reduce(function (result, pair) { - var parts = pair.split('='); - result[parts[0]] = parts[1]; - return result; - }, {}); - - if (adUnitInfo.bidstatus === '1') { - dimensions = adUnitInfo.bidid.split('@')[1].split('x'); - adResponse = bidfactory.createBid(1); - adResponse.bidderCode = 'pubmatic'; - adResponse.adSlot = bid.adSlot; - adResponse.cpm = Number(adUnitInfo.bid); - adResponse.ad = unescape(adUnit.creative_tag); - adResponse.ad += utils.createTrackPixelIframeHtml(decodeURIComponent(adUnit.tracking_url)); - adResponse.width = dimensions[0]; - adResponse.height = dimensions[1]; - adResponse.dealId = adUnitInfo.wdeal; - - bidmanager.addBidResponse(bids[i].placementCode, adResponse); - } else { - // Indicate an ad was not returned - adResponse = bidfactory.createBid(2); - adResponse.bidderCode = 'pubmatic'; - bidmanager.addBidResponse(bids[i].placementCode, adResponse); + if (utils.isStr(value)) { + conf[key] = value; + } else { + utils.logWarn('PubMatic: Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); + } } } + } + return conf; +} + +function _createOrtbTemplate(conf) { + return { + id: '' + new Date().getTime(), + at: AUCTION_TYPE, + cur: [CURRENCY], + imp: [], + site: { + page: conf.pageURL, + ref: conf.refURL, + publisher: {} + }, + device: { + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + h: screen.height, + w: screen.width, + language: navigator.language + }, + user: {}, + ext: {} }; +} +function _createImpressionObject(bid, conf) { return { - callBids: _callBids + id: bid.bidId, + tagid: bid.params.adUnit, + bidfloor: _parseSlotParam('kadfloor', bid.params.kadfloor), + secure: window.location.protocol === 'https:' ? 1 : 0, + banner: { + pos: 0, + w: bid.params.width, + h: bid.params.height, + topframe: utils.inIframe() ? 0 : 1, + }, + ext: { + pmZoneId: _parseSlotParam('pmzoneid', bid.params.pmzoneid) + } }; } -adaptermanager.registerBidAdapter(new PubmaticAdapter(), 'pubmatic'); +export const spec = { + code: BIDDER_CODE, + + /** + * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: bid => { + if (bid && bid.params) { + if (!utils.isStr(bid.params.publisherId)) { + utils.logWarn('PubMatic Error: publisherId is mandatory and cannot be numeric. Call to OpenBid will not be sent.'); + return false; + } + if (!utils.isStr(bid.params.adSlot)) { + utils.logWarn('PubMatic: adSlotId is mandatory and cannot be numeric. Call to OpenBid will not be sent.'); + return false; + } + return true; + } + return false; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (validBidRequests, bidderRequest) => { + var conf = _initConf(); + var payload = _createOrtbTemplate(conf); + validBidRequests.forEach(bid => { + _parseAdSlot(bid); + if (!(bid.params.adSlot && bid.params.adUnit && bid.params.adUnitIndex && bid.params.width && bid.params.height)) { + utils.logWarn('PubMatic: Skipping the non-standard adslot:', bid.params.adSlot, bid); + return; + } + conf.pubId = conf.pubId || bid.params.publisherId; + conf = _handleCustomParams(bid.params, conf); + conf.transactionId = bid.transactionId; + payload.imp.push(_createImpressionObject(bid, conf)); + }); + + if (payload.imp.length == 0) { + return; + } + + payload.site.publisher.id = conf.pubId.trim(); + publisherId = conf.pubId.trim(); + payload.ext.wrapper = {}; + payload.ext.wrapper.profile = parseInt(conf.profId) || UNDEFINED; + payload.ext.wrapper.version = parseInt(conf.verId) || UNDEFINED; + payload.ext.wrapper.wiid = conf.wiid || UNDEFINED; + payload.ext.wrapper.wv = constants.REPO_AND_VERSION; + payload.ext.wrapper.transactionId = conf.transactionId; + payload.ext.wrapper.wp = 'pbjs'; + payload.user.gender = (conf.gender ? conf.gender.trim() : UNDEFINED); + payload.user.geo = {}; + + // Attaching GDPR Consent Params + if (bidderRequest && bidderRequest.gdprConsent) { + payload.user.ext = { + consent: bidderRequest.gdprConsent.consentString + }; + + payload.regs = { + ext: { + gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0) + } + }; + } + + payload.user.geo.lat = _parseSlotParam('lat', conf.lat); + payload.user.geo.lon = _parseSlotParam('lon', conf.lon); + payload.user.yob = _parseSlotParam('yob', conf.yob); + payload.device.geo = {}; + payload.device.geo.lat = _parseSlotParam('lat', conf.lat); + payload.device.geo.lon = _parseSlotParam('lon', conf.lon); + payload.site.page = conf.kadpageurl.trim() || payload.site.page.trim(); + payload.site.domain = _getDomainFromURL(payload.site.page); + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(payload) + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} response A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (response, request) => { + const bidResponses = []; + try { + if (response.body && response.body.seatbid && response.body.seatbid[0] && response.body.seatbid[0].bid) { + response.body.seatbid[0].bid.forEach(bid => { + let newBid = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + dealId: bid.dealid, + currency: CURRENCY, + netRevenue: NET_REVENUE, + ttl: 300, + referrer: utils.getTopWindowUrl(), + ad: bid.adm + }; + + if (bid.ext && bid.ext.deal_channel) { + newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; + } + + bidResponses.push(newBid); + }); + } + } catch (error) { + utils.logError(error); + } + return bidResponses; + }, + + /** + * Register User Sync. + */ + getUserSyncs: (syncOptions, responses, gdprConsent) => { + let syncurl = USYNCURL + publisherId; + + // Attaching GDPR Consent Params in UserSync url + if (gdprConsent) { + syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: syncurl + }]; + } else { + utils.logWarn('PubMatic: Please enable iframe based user sync.'); + } + } +}; -module.exports = PubmaticAdapter; +registerBidder(spec); diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md new file mode 100644 index 00000000000..768b3c541f6 --- /dev/null +++ b/modules/pubmaticBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: PubMatic Bid Adapter +Module Type: Bidder Adapter +Maintainer: header-bidding@pubmatic.com +``` + +# Description + +Connects to PubMatic exchange for bids. + +PubMatic bid adapter supports Banner currently. + +# Sample Ad Unit: For Publishers +``` +var adUnits = [ +{ + code: 'test-div', + sizes: [ + [300, 250], + [728, 90] + ], + bids: [{ + bidder: 'pubmatic', + params: { + publisherId: '156209', // required + adSlot: 'pubmatic_test2@300x250', // required + pmzoneid: 'zone1, zone11', // optional + lat: '40.712775', // optional + lon: '-74.005973', // optional + yob: '1982', // optional + kadpageurl: 'www.test.com', // optional + gender: 'M', // optional + kadfloor: '0.50' // optional + } + }] +} +``` + +### Configuration + +PubMatic recommends the UserSync configuration below. Without it, the PubMatic adapter will not able to perform user syncs, which lowers match rate and reduces monetization. + +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true, + enabledBidders: ['pubmatic'], + syncDelay: 6000 + }}); +``` +Note: Combine the above the configuration with any other UserSync configuration. Multiple setConfig() calls overwrite each other and only last call for a given attribute will take effect. diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js index 96ccb26f513..33c8483d16b 100644 --- a/modules/pubwiseAnalyticsAdapter.js +++ b/modules/pubwiseAnalyticsAdapter.js @@ -23,7 +23,8 @@ const utils = require('src/utils'); const analyticsType = 'endpoint'; const analyticsName = 'PubWise Analytics: '; let defaultUrl = 'https://api.pubwise.io/api/v4/event/default/'; -let pubwiseVersion = '2.2'; +let pubwiseVersion = '3.0'; +let pubwiseSchema = 'AVOCET'; let configOptions = {site: '', endpoint: 'https://api.pubwise.io/api/v4/event/default/', debug: ''}; let pwAnalyticsEnabled = false; let utmKeys = {utm_source: '', utm_medium: '', utm_campaign: '', utm_term: '', utm_content: ''}; @@ -49,8 +50,7 @@ function enrichWithUTM(dataBag) { let newUtm = false; try { for (let prop in utmKeys) { - let urlValue = utils.getParameterByName(prop); - utmKeys[prop] = urlValue; + utmKeys[prop] = utils.getParameterByName(prop); if (utmKeys[prop] != '') { newUtm = true; dataBag[prop] = utmKeys[prop]; @@ -84,12 +84,13 @@ function sendEvent(eventType, data) { eventType: eventType, args: data, target_site: configOptions.site, + pubwiseSchema: pubwiseSchema, debug: configOptions.debug ? 1 : 0, }; + dataBag = enrichWithMetrics(dataBag); // for certain events, track additional info if (eventType == CONSTANTS.EVENTS.AUCTION_INIT) { - dataBag = enrichWithMetrics(dataBag); dataBag = enrichWithUTM(dataBag); } diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 82c8df9a9c1..94733ad7805 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -1,85 +1,343 @@ -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader.js'); -var utils = require('src/utils.js'); -var adaptermanager = require('src/adaptermanager'); - -var PulsePointAdapter = function PulsePointAdapter() { - var getJsStaticUrl = window.location.protocol + '//tag-st.contextweb.com/getjs.static.js'; - var bidUrl = window.location.protocol + '//bid.contextweb.com/header/tag'; - - function _callBids(params) { - if (typeof window.pp === 'undefined') { - adloader.loadScript(getJsStaticUrl, function () { bid(params); }, true); - } else { - bid(params); - } - } +/* eslint dot-notation:0, quote-props:0 */ +import {logError, getTopWindowLocation} from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const NATIVE_DEFAULTS = { + TITLE_LEN: 100, + DESCR_LEN: 200, + SPONSORED_BY_LEN: 50, + IMG_MIN: 150, + ICON_MIN: 50, +}; + +const DEFAULT_BID_TTL = 20; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; + +/** + * PulsePoint Bid Adapter. + * Contact: ExchangeTeam@pulsepoint.com + * + * Aliases - pulseLite and pulsepointLite are supported for backwards compatibility. + * Formats - Display/Native/Outstream formats supported. + * + */ +export const spec = { + + code: 'pulsepoint', + + aliases: ['pulseLite', 'pulsepointLite'], + + supportedMediaTypes: ['banner', 'native'], + + isBidRequestValid: bid => ( + !!(bid && bid.params && bid.params.cp && bid.params.ct) + ), + + buildRequests: (bidRequests, bidderRequest) => { + const request = { + id: bidRequests[0].bidderRequestId, + imp: bidRequests.map(slot => impression(slot)), + site: site(bidRequests), + app: app(bidRequests), + device: device(), + }; + applyGdpr(bidderRequest, request); + return { + method: 'POST', + url: '//bid.contextweb.com/header/ortb', + data: JSON.stringify(request), + }; + }, + + interpretResponse: (response, request) => ( + bidResponseAvailable(request, response) + ), - function bid(params) { - var bids = params.bids; - for (var i = 0; i < bids.length; i++) { - var bidRequest = bids[i]; - requestBid(bidRequest); + getUserSyncs: syncOptions => { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: '//bh.contextweb.com/visitormatch' + }]; + } else if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: '//bh.contextweb.com/visitormatch/prebid' + }]; } } - function requestBid(bidRequest) { - try { - var ppBidRequest = new window.pp.Ad(bidRequestOptions(bidRequest)); - ppBidRequest.display(); - } catch (e) { - // register passback on any exceptions while attempting to fetch response. - utils.logError('pulsepoint.requestBid', 'ERROR', e); - bidResponseAvailable(bidRequest); +}; + +/** + * Callback for bids, after the call to PulsePoint completes. + */ +function bidResponseAvailable(bidRequest, bidResponse) { + const idToImpMap = {}; + const idToBidMap = {}; + bidResponse = bidResponse.body + // extract the request bids and the response bids, keyed by impr-id + const ortbRequest = parse(bidRequest.data); + ortbRequest.imp.forEach(imp => { + idToImpMap[imp.id] = imp; + }); + if (bidResponse) { + bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => { + idToBidMap[bid.impid] = bid; + })); + } + const bids = []; + Object.keys(idToImpMap).forEach(id => { + if (idToBidMap[id]) { + const bid = { + requestId: id, + cpm: idToBidMap[id].price, + creative_id: id, + creativeId: id, + adId: id, + ttl: DEFAULT_BID_TTL, + netRevenue: DEFAULT_NET_REVENUE, + currency: DEFAULT_CURRENCY + }; + if (idToImpMap[id]['native']) { + bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); + bid.mediaType = 'native'; + } else { + bid.ad = idToBidMap[id].adm; + bid.width = idToImpMap[id].banner.w; + bid.height = idToImpMap[id].banner.h; + } + applyExt(bid, idToBidMap[id]) + bids.push(bid); } + }); + return bids; +} + +function applyExt(bid, ortbBid) { + if (ortbBid && ortbBid.ext) { + bid.ttl = ortbBid.ext.ttl || bid.ttl; + bid.currency = ortbBid.ext.currency || bid.currency; + bid.netRevenue = ortbBid.ext.netRevenue != null ? ortbBid.ext.netRevenue : bid.netRevenue; } +} + +/** + * Produces an OpenRTBImpression from a slot config. + */ +function impression(slot) { + return { + id: slot.bidId, + banner: banner(slot), + 'native': nativeImpression(slot), + tagid: slot.params.ct.toString(), + }; +} + +/** + * Produces an OpenRTB Banner object for the slot given. + */ +function banner(slot) { + const size = adSize(slot); + return slot.nativeParams ? null : { + w: size[0], + h: size[1], + }; +} - function bidRequestOptions(bidRequest) { - var callback = bidResponseCallback(bidRequest); - var options = { - cn: 1, - ca: window.pp.requestActions.BID, - cu: bidUrl, - adUnitId: bidRequest.placementCode, - callback: callback +/** + * Produces an OpenRTB Native object for the slot given. + */ +function nativeImpression(slot) { + if (slot.nativeParams) { + const assets = []; + addAsset(assets, titleAsset(assets.length + 1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN)); + addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN)); + addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN)); + addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN)); + addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN)); + return { + request: JSON.stringify({ assets }), + ver: '1.1', }; - for (var param in bidRequest.params) { - if (bidRequest.params.hasOwnProperty(param)) { - options[param] = bidRequest.params[param]; - } - } - return options; } + return null; +} + +/** + * Helper method to add an asset to the assets list. + */ +function addAsset(assets, asset) { + if (asset) { + assets.push(asset); + } +} - function bidResponseCallback(bid) { - return function (bidResponse) { - bidResponseAvailable(bid, bidResponse); +/** + * Produces a Native Title asset for the configuration given. + */ +function titleAsset(id, params, defaultLen) { + if (params) { + return { + id, + required: params.required ? 1 : 0, + title: { + len: params.len || defaultLen, + }, }; } + return null; +} + +/** + * Produces a Native Image asset for the configuration given. + */ +function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { + return params ? { + id, + required: params.required ? 1 : 0, + img: { + type, + wmin: params.wmin || defaultMinWidth, + hmin: params.hmin || defaultMinHeight, + } + } : null; +} + +/** + * Produces a Native Data asset for the configuration given. + */ +function dataAsset(id, params, type, defaultLen) { + return params ? { + id, + required: params.required ? 1 : 0, + data: { + type, + len: params.len || defaultLen, + } + } : null; +} + +/** + * Produces an OpenRTB site object. + */ +function site(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.cp : '0'; + const appParams = bidderRequest[0].params.app; + if (!appParams) { + return { + publisher: { + id: pubId.toString(), + }, + ref: referrer(), + page: getTopWindowLocation().href, + } + } + return null; +} - function bidResponseAvailable(bidRequest, bidResponse) { - if (bidResponse) { - var adSize = bidRequest.params.cf.toUpperCase().split('X'); - var bid = bidfactory.createBid(1, bidRequest); - bid.bidderCode = bidRequest.bidder; - bid.cpm = bidResponse.bidCpm; - bid.ad = bidResponse.html; - bid.width = adSize[0]; - bid.height = adSize[1]; - bidmanager.addBidResponse(bidRequest.placementCode, bid); - } else { - var passback = bidfactory.createBid(2, bidRequest); - passback.bidderCode = bidRequest.bidder; - bidmanager.addBidResponse(bidRequest.placementCode, passback); +/** + * Produces an OpenRTB App object. + */ +function app(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.cp : '0'; + const appParams = bidderRequest[0].params.app; + if (appParams) { + return { + publisher: { + id: pubId.toString(), + }, + bundle: appParams.bundle, + storeurl: appParams.storeUrl, + domain: appParams.domain, } } + return null; +} + +/** + * Attempts to capture the referrer url. + */ +function referrer() { + try { + return window.top.document.referrer; + } catch (e) { + return document.referrer; + } +} +/** + * Produces an OpenRTB Device object. + */ +function device() { return { - callBids: _callBids + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), }; -}; +} + +/** + * Safely parses the input given. Returns null on + * parsing failure. + */ +function parse(rawResponse) { + try { + if (rawResponse) { + return JSON.parse(rawResponse); + } + } catch (ex) { + logError('pulsepointLite.safeParse', 'ERROR', ex); + } + return null; +} + +/** + * Determines the AdSize for the slot. + */ +function adSize(slot) { + if (slot.params.cf) { + const size = slot.params.cf.toUpperCase().split('X'); + const width = parseInt(slot.params.cw || size[0], 10); + const height = parseInt(slot.params.ch || size[1], 10); + return [width, height]; + } + return [1, 1]; +} + +/** + * Applies GDPR parameters to request. + */ +function applyGdpr(bidderRequest, ortbRequest) { + if (bidderRequest && bidderRequest.gdprConsent) { + ortbRequest.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; + ortbRequest.user = { ext: { consent: bidderRequest.gdprConsent.consentString } }; + } +} -adaptermanager.registerBidAdapter(new PulsePointAdapter(), 'pulsepoint'); +/** + * Parses the native response from the Bid given. + */ +function nativeResponse(imp, bid) { + if (imp['native']) { + const nativeAd = parse(bid.adm); + const keys = {}; + if (nativeAd && nativeAd['native'] && nativeAd['native'].assets) { + nativeAd['native'].assets.forEach(asset => { + keys.title = asset.title ? asset.title.text : keys.title; + keys.body = asset.data && asset.data.type === 2 ? asset.data.value : keys.body; + keys.sponsoredBy = asset.data && asset.data.type === 1 ? asset.data.value : keys.sponsoredBy; + keys.image = asset.img && asset.img.type === 3 ? asset.img.url : keys.image; + keys.icon = asset.img && asset.img.type === 1 ? asset.img.url : keys.icon; + }); + if (nativeAd['native'].link) { + keys.clickUrl = encodeURIComponent(nativeAd['native'].link.url); + } + keys.impressionTrackers = nativeAd['native'].imptrackers; + return keys; + } + } + return null; +} -module.exports = PulsePointAdapter; +registerBidder(spec); diff --git a/modules/pulsepointLiteBidAdapter.md b/modules/pulsepointBidAdapter.md similarity index 77% rename from modules/pulsepointLiteBidAdapter.md rename to modules/pulsepointBidAdapter.md index 23c96758ca0..1b119f0499f 100644 --- a/modules/pulsepointLiteBidAdapter.md +++ b/modules/pulsepointBidAdapter.md @@ -1,6 +1,6 @@ # Overview -**Module Name**: PulsePoint Lite Bidder Adapter +**Module Name**: PulsePoint Bidder Adapter **Module Type**: Bidder Adapter **Maintainer**: ExchangeTeam@pulsepoint.com @@ -8,7 +8,8 @@ Connects to PulsePoint demand source to fetch bids. Banner, Outstream and Native formats are supported. -Please use ```pulseLite``` as the bidder code. +Please use ```pulsepoint``` as the bidder code. +```pulseLite``` and ```pulsepointLite``` aliases also supported as well. # Test Parameters ``` @@ -16,7 +17,7 @@ Please use ```pulseLite``` as the bidder code. code: 'banner-ad-div', sizes: [[300, 250]], bids: [{ - bidder: 'pulsepointLite', + bidder: 'pulsepoint', params: { cf: '300X250', cp: 512379, @@ -33,7 +34,7 @@ Please use ```pulseLite``` as the bidder code. sponsoredBy: { len: 20 } }, bids: [{ - bidder: 'pulseLite', + bidder: 'pulsepoint', params: { cp: 512379, ct: 505642 diff --git a/modules/pulsepointLiteBidAdapter.js b/modules/pulsepointLiteBidAdapter.js deleted file mode 100644 index 00b5c014e98..00000000000 --- a/modules/pulsepointLiteBidAdapter.js +++ /dev/null @@ -1,313 +0,0 @@ -/* eslint dot-notation:0, quote-props:0 */ -import {logError, getTopWindowLocation} from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; - -const NATIVE_DEFAULTS = { - TITLE_LEN: 100, - DESCR_LEN: 200, - SPONSORED_BY_LEN: 50, - IMG_MIN: 150, - ICON_MIN: 50, -}; - -/** - * PulsePoint "Lite" Adapter. This adapter implementation is lighter than the - * alternative/original PulsePointAdapter because it has no external - * dependencies and relies on a single OpenRTB request to the PulsePoint - * bidder instead of separate requests per slot. - */ -export const spec = { - - code: 'pulseLite', - - aliases: ['pulsepointLite'], - - supportedMediaTypes: ['native'], - - isBidRequestValid: bid => ( - !!(bid && bid.params && bid.params.cp && bid.params.ct) - ), - - buildRequests: bidRequests => { - const request = { - id: bidRequests[0].bidderRequestId, - imp: bidRequests.map(slot => impression(slot)), - site: site(bidRequests), - app: app(bidRequests), - device: device(), - }; - return { - method: 'POST', - url: '//bid.contextweb.com/header/ortb', - data: JSON.stringify(request), - }; - }, - - interpretResponse: (response, request) => ( - bidResponseAvailable(request, response) - ), - - getUserSyncs: syncOptions => { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: '//bh.contextweb.com/visitormatch' - }]; - } else if (syncOptions.pixelEnabled) { - return [{ - type: 'image', - url: '//bh.contextweb.com/visitormatch/prebid' - }]; - } - } - -}; - -/** - * Callback for bids, after the call to PulsePoint completes. - */ -function bidResponseAvailable(bidRequest, bidResponse) { - const idToImpMap = {}; - const idToBidMap = {}; - // extract the request bids and the response bids, keyed by impr-id - const ortbRequest = parse(bidRequest.data); - ortbRequest.imp.forEach(imp => { - idToImpMap[imp.id] = imp; - }); - if (bidResponse) { - bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => { - idToBidMap[bid.impid] = bid; - })); - } - const bids = []; - Object.keys(idToImpMap).forEach(id => { - if (idToBidMap[id]) { - const bid = { - requestId: id, - cpm: idToBidMap[id].price, - creative_id: id, - creativeId: id, - adId: id, - }; - if (idToImpMap[id]['native']) { - bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); - bid.mediaType = 'native'; - } else { - bid.ad = idToBidMap[id].adm; - bid.width = idToImpMap[id].banner.w; - bid.height = idToImpMap[id].banner.h; - } - bids.push(bid); - } - }); - return bids; -} - -/** - * Produces an OpenRTBImpression from a slot config. - */ -function impression(slot) { - return { - id: slot.bidId, - banner: banner(slot), - 'native': nativeImpression(slot), - tagid: slot.params.ct.toString(), - }; -} - -/** - * Produces an OpenRTB Banner object for the slot given. - */ -function banner(slot) { - const size = adSize(slot); - return slot.nativeParams ? null : { - w: size[0], - h: size[1], - }; -} - -/** - * Produces an OpenRTB Native object for the slot given. - */ -function nativeImpression(slot) { - if (slot.nativeParams) { - const assets = []; - addAsset(assets, titleAsset(assets.length + 1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN)); - addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN)); - addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN)); - addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN)); - addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN)); - return { - request: JSON.stringify({ assets }), - ver: '1.1', - }; - } - return null; -} - -/** - * Helper method to add an asset to the assets list. - */ -function addAsset(assets, asset) { - if (asset) { - assets.push(asset); - } -} - -/** - * Produces a Native Title asset for the configuration given. - */ -function titleAsset(id, params, defaultLen) { - if (params) { - return { - id, - required: params.required ? 1 : 0, - title: { - len: params.len || defaultLen, - }, - }; - } - return null; -} - -/** - * Produces a Native Image asset for the configuration given. - */ -function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { - return params ? { - id, - required: params.required ? 1 : 0, - img: { - type, - wmin: params.wmin || defaultMinWidth, - hmin: params.hmin || defaultMinHeight, - } - } : null; -} - -/** - * Produces a Native Data asset for the configuration given. - */ -function dataAsset(id, params, type, defaultLen) { - return params ? { - id, - required: params.required ? 1 : 0, - data: { - type, - len: params.len || defaultLen, - } - } : null; -} - -/** - * Produces an OpenRTB site object. - */ -function site(bidderRequest) { - const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.cp : '0'; - const appParams = bidderRequest[0].params.app; - if (!appParams) { - return { - publisher: { - id: pubId.toString(), - }, - ref: referrer(), - page: getTopWindowLocation().href, - } - } - return null; -} - -/** - * Produces an OpenRTB App object. - */ -function app(bidderRequest) { - const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.cp : '0'; - const appParams = bidderRequest[0].params.app; - if (appParams) { - return { - publisher: { - id: pubId.toString(), - }, - bundle: appParams.bundle, - storeurl: appParams.storeUrl, - domain: appParams.domain, - } - } - return null; -} - -/** - * Attempts to capture the referrer url. - */ -function referrer() { - try { - return window.top.document.referrer; - } catch (e) { - return document.referrer; - } -} - -/** - * Produces an OpenRTB Device object. - */ -function device() { - return { - ua: navigator.userAgent, - language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), - }; -} - -/** - * Safely parses the input given. Returns null on - * parsing failure. - */ -function parse(rawResponse) { - try { - if (rawResponse) { - return JSON.parse(rawResponse); - } - } catch (ex) { - logError('pulsepointLite.safeParse', 'ERROR', ex); - } - return null; -} - -/** - * Determines the AdSize for the slot. - */ -function adSize(slot) { - if (slot.params.cf) { - const size = slot.params.cf.toUpperCase().split('X'); - const width = parseInt(slot.params.cw || size[0], 10); - const height = parseInt(slot.params.ch || size[1], 10); - return [width, height]; - } - return [1, 1]; -} - -/** - * Parses the native response from the Bid given. - */ -function nativeResponse(imp, bid) { - if (imp['native']) { - const nativeAd = parse(bid.adm); - const keys = {}; - if (nativeAd && nativeAd['native'] && nativeAd['native'].assets) { - nativeAd['native'].assets.forEach(asset => { - keys.title = asset.title ? asset.title.text : keys.title; - keys.body = asset.data && asset.data.type === 2 ? asset.data.value : keys.body; - keys.sponsoredBy = asset.data && asset.data.type === 1 ? asset.data.value : keys.sponsoredBy; - keys.image = asset.img && asset.img.type === 3 ? asset.img.url : keys.image; - keys.icon = asset.img && asset.img.type === 1 ? asset.img.url : keys.icon; - }); - if (nativeAd['native'].link) { - keys.clickUrl = encodeURIComponent(nativeAd['native'].link.url); - } - keys.impressionTrackers = nativeAd['native'].imptrackers; - return keys; - } - } - return null; -} - -registerBidder(spec); diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index f199e9fad30..f10fd48502f 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -1,128 +1,165 @@ -const utils = require('src/utils.js'); -const bidfactory = require('src/bidfactory.js'); -const bidmanager = require('src/bidmanager.js'); -const ajax = require('src/ajax.js'); -const CONSTANTS = require('src/constants.json'); -const adaptermanager = require('src/adaptermanager'); -const QUANTCAST_CALLBACK_URL = 'http://global.qc.rtb.quantserve.com:8080/qchb'; - -var QuantcastAdapter = function QuantcastAdapter() { - const BIDDER_CODE = 'quantcast'; - - const DEFAULT_BID_FLOOR = 0.0000000001; - let bidRequests = {}; - - let returnEmptyBid = function(bidId) { - var bidRequested = utils.getBidRequest(bidId); - if (!utils.isEmpty(bidRequested)) { - let bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bidRequested); - bid.bidderCode = BIDDER_CODE; - bidmanager.addBidResponse(bidRequested.placementCode, bid); +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'quantcast'; +const DEFAULT_BID_FLOOR = 0.0000000001; + +export const QUANTCAST_CALLBACK_URL = 'global.qc.rtb.quantserve.com'; +export const QUANTCAST_CALLBACK_URL_TEST = 's2s-canary.quantserve.com'; +export const QUANTCAST_NET_REVENUE = true; +export const QUANTCAST_TEST_PUBLISHER = 'test-publisher'; +export const QUANTCAST_TTL = 4; + +/** + * The documentation for Prebid.js Adapter 1.0 can be found at link below, + * http://prebid.org/dev-docs/bidder-adapter-1.html + */ +export const spec = { + code: BIDDER_CODE, + + /** + * Verify the `AdUnits.bids` response with `true` for valid request and `false` + * for invalid request. + * + * @param {object} bid + * @return boolean `true` is this is a valid bid, and `false` otherwise + */ + isBidRequestValid(bid) { + if (!bid) { + return false; } - }; - // expose the callback to the global object: - $$PREBID_GLOBAL$$.handleQuantcastCB = function (responseText) { - if (utils.isEmpty(responseText)) { - return; - } - let response = null; - try { - response = JSON.parse(responseText); - } catch (e) { - // Malformed JSON - utils.logError("Malformed JSON received from server - can't do anything here"); - return; - } - - if (response === null || !response.hasOwnProperty('bids') || utils.isEmpty(response.bids)) { - utils.logError("Sub-optimal JSON received from server - can't do anything here"); - return; + if (bid.mediaType === 'video') { + return false; } - for (let i = 0; i < response.bids.length; i++) { - let seatbid = response.bids[i]; - let key = seatbid.placementCode; - var request = bidRequests[key]; - if (request === null || request === undefined) { - return returnEmptyBid(seatbid.placementCode); - } - // This line is required since this is the field - // that bidfactory.createBid looks for - request.bidId = request.imp[0].placementCode; - let responseBid = bidfactory.createBid(CONSTANTS.STATUS.GOOD, request); - - responseBid.cpm = seatbid.cpm; - responseBid.ad = seatbid.ad; - responseBid.height = seatbid.height; - responseBid.width = seatbid.width; - responseBid.bidderCode = response.bidderCode; - responseBid.requestId = request.requestId; - responseBid.bidderCode = BIDDER_CODE; - - bidmanager.addBidResponse(request.bidId, responseBid); + return true; + }, + + /** + * Make a server request when the page asks Prebid.js for bids from a list of + * `BidRequests`. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be send to Quantcast server + * @return ServerRequest information describing the request to the server. + */ + buildRequests(bidRequests) { + const bids = bidRequests || []; + + const referrer = utils.getTopWindowUrl(); + const loc = utils.getTopWindowLocation(); + const domain = loc.hostname; + + let publisherTagURL; + let publisherTagURLTest; + + // Switch the callback URL to Quantcast Canary Endpoint for testing purpose + // `//` is not used because we have different port setting at our end + switch (window.location.protocol) { + case 'https:': + publisherTagURL = `https://${QUANTCAST_CALLBACK_URL}:8443/qchb`; + publisherTagURLTest = `https://${QUANTCAST_CALLBACK_URL_TEST}:8443/qchb`; + break; + default: + publisherTagURL = `http://${QUANTCAST_CALLBACK_URL}:8080/qchb`; + publisherTagURLTest = `http://${QUANTCAST_CALLBACK_URL_TEST}:8080/qchb`; } - }; - function callBids(params) { - let bids = params.bids || []; - if (bids.length === 0) { - return; - } - - let referrer = utils.getTopWindowUrl(); - let loc = utils.getTopWindowLocation(); - let domain = loc.hostname; - let publisherId = 0; + const bidRequestsList = bids.map(bid => { + const bidSizes = []; - publisherId = '' + bids[0].params.publisherId; - utils._each(bids, function(bid) { - let key = bid.placementCode; - var bidSizes = []; - utils._each(bid.sizes, function (size) { + bid.sizes.forEach(size => { bidSizes.push({ - 'width': size[0], - 'height': size[1] + width: size[0], + height: size[1] }); }); - bidRequests[key] = bidRequests[key] || { - 'publisherId': publisherId, - 'requestId': bid.bidId, - 'bidId': bid.bidId, - 'site': { - 'page': loc.href, - 'referrer': referrer, - 'domain': domain, + // Request Data Format can be found at https://wiki.corp.qc/display/adinf/QCX + const requestData = { + publisherId: bid.params.publisherId, + requestId: bid.bidId, + imp: [ + { + banner: { + battr: bid.params.battr, + sizes: bidSizes + }, + placementCode: bid.placementCode, + bidFloor: bid.params.bidFloor || DEFAULT_BID_FLOOR + } + ], + site: { + page: loc.href, + referrer, + domain }, - 'imp': [{ - - 'banner': { - 'battr': bid.params.battr, - 'sizes': bidSizes, - }, - 'placementCode': bid.placementCode, - 'bidFloor': bid.params.bidFloor || DEFAULT_BID_FLOOR, - }] + bidId: bid.bidId }; - }); - utils._each(bidRequests, function (bidRequest) { - ajax.ajax(QUANTCAST_CALLBACK_URL, $$PREBID_GLOBAL$$.handleQuantcastCB, JSON.stringify(bidRequest), { + const data = JSON.stringify(requestData); + + const url = + bid.params.publisherId === QUANTCAST_TEST_PUBLISHER + ? publisherTagURLTest + : publisherTagURL; + + return { + data, method: 'POST', - withCredentials: true - }); + url + }; }); - } - // Export the `callBids` function, so that Prebid.js can execute - // this function when the page asks to send out bid requests. - return { - callBids: callBids, - QUANTCAST_CALLBACK_URL: QUANTCAST_CALLBACK_URL - }; -}; + return bidRequestsList; + }, + + /** + * Function get called when the browser has received the response from Quantcast server. + * The function parse the response and create a `bidResponse` object containing one/more bids. + * Returns an empty array if no valid bids + * + * Response Data Format can be found at https://wiki.corp.qc/display/adinf/QCX + * + * @param {*} serverResponse A successful response from Quantcast server. + * @return {Bid[]} An array of bids which were nested inside the server. + * + */ + interpretResponse(serverResponse) { + if (serverResponse === undefined) { + utils.logError('Server Response is undefined'); + return []; + } + + const response = serverResponse['body']; + + if ( + response === undefined || + !response.hasOwnProperty('bids') || + utils.isEmpty(response.bids) + ) { + utils.logError('Sub-optimal JSON received from Quantcast server'); + return []; + } -adaptermanager.registerBidAdapter(new QuantcastAdapter(), 'quantcast'); + const bidResponsesList = response.bids.map(bid => { + const { ad, cpm, width, height, creativeId, currency } = bid; + + return { + requestId: response.requestId, + cpm, + width, + height, + ad, + ttl: QUANTCAST_TTL, + creativeId, + netRevenue: QUANTCAST_NET_REVENUE, + currency + }; + }); + + return bidResponsesList; + } +}; -module.exports = QuantcastAdapter; +registerBidder(spec); diff --git a/modules/quantcastBidAdapter.md b/modules/quantcastBidAdapter.md new file mode 100644 index 00000000000..20cf25bffbf --- /dev/null +++ b/modules/quantcastBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Quantcast Bidder Adapter +Module Type: Bidder Adapter +Maintainer: xli@quantcast.com +``` + +# Description + +Module that connects to Quantcast demand sources to fetch bids. + +# Test Parameters + +```js +const adUnits = [{ + code: 'banner', + sizes: [ + [300, 250] + ], + bids: [ + { + bidder: 'quantcast', + params: { + publisherId: 'test-publisher', // REQUIRED - Publisher ID provided by Quantcast + battr: [1, 2] // OPTIONAL - Array of blocked creative attributes as per OpenRTB Spec List 5.3 + } + } + ] +}]; +``` \ No newline at end of file diff --git a/modules/quantumBidAdapter.js b/modules/quantumBidAdapter.js new file mode 100644 index 00000000000..f6df8a2ff61 --- /dev/null +++ b/modules/quantumBidAdapter.js @@ -0,0 +1,305 @@ +import * as utils from 'src/utils'; +import { BANNER, NATIVE } from 'src/mediaTypes'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'quantum'; +const ENDPOINT_URL = '//s.sspqns.com/hb'; +export const spec = { + code: BIDDER_CODE, + aliases: ['quantx', 'qtx'], // short code + supportedMediaTypes: [BANNER, NATIVE], + + /** + * 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 && bid.params.placementId); + }, + /** + * 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 (bidRequests) { + return bidRequests.map(bid => { + const qtxRequest = {}; + let bidId = ''; + + const params = bid.params; + let placementId = params.placementId; + + let devEnpoint = false; + if (params.useDev && params.useDev === '1') { + devEnpoint = '//sdev.sspqns.com/hb'; + } + let renderMode = 'native'; + for (let i = 0; i < bid.sizes.length; i++) { + if (bid.sizes[i][0] > 1 && bid.sizes[i][1] > 1) { + renderMode = 'banner'; + break; + } + } + + let mediaType = (bid.mediaType === 'native' || utils.deepAccess(bid, 'mediaTypes.native')) ? 'native' : 'banner'; + + if (mediaType === 'native') { + renderMode = 'native'; + } + + if (!bidId) { + bidId = bid.bidId; + } + qtxRequest.auid = placementId; + const url = devEnpoint || ENDPOINT_URL; + + return { + method: 'GET', + bidId: bidId, + sizes: bid.sizes, + mediaType: mediaType, + renderMode: renderMode, + url: url, + 'data': qtxRequest + }; + }); + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const serverBody = serverResponse.body; + const bidResponses = []; + let responseCPM; + let bid = {}; + let id = bidRequest.bidId; + + if (serverBody.price && serverBody.price !== 0) { + responseCPM = parseFloat(serverBody.price); + + bid.creativeId = serverBody.creative_id || '0'; + bid.cpm = responseCPM; + bid.requestId = bidRequest.bidId; + bid.width = 1; + bid.height = 1; + bid.ttl = 200; + bid.netRevenue = true; + bid.currency = 'USD'; + + if (serverBody.native) { + bid.native = serverBody.native; + } + if (serverBody.cobj) { + bid.cobj = serverBody.cobj; + } + if (bidRequest.sizes) { + bid.width = bidRequest.sizes[0][0]; + bid.height = bidRequest.sizes[0][1]; + } + + bid.nurl = serverBody.nurl; + bid.sync = serverBody.sync; + if (bidRequest.renderMode && bidRequest.renderMode === 'banner') { + bid.mediaType = 'banner'; + if (serverBody.native) { + const adAssetsUrl = '//cdn.elasticad.net/native/serve/js/quantx/quantumAd/'; + let assets = serverBody.native.assets; + let link = serverBody.native.link; + + let trackers = []; + if (serverBody.native.imptrackers) { + trackers = serverBody.native.imptrackers; + } + + let jstracker = ''; + if (serverBody.native.jstracker) { + jstracker = serverBody.native.jstracker; + } + + if (serverBody.nurl) { + trackers.push(serverBody.nurl); + } + + let ad = {}; + ad['trackers'] = trackers; + ad['jstrackers'] = jstracker; + + for (let i = 0; i < assets.length; i++) { + let asset = assets[i]; + switch (asset['id']) { + case 1: + ad['title'] = asset['title']['text']; + break; + case 2: + ad['sponsor_logo'] = asset['img']['url']; + break; + case 3: + ad['content'] = asset['data']['value']; + break; + case 4: + ad['main_image'] = asset['img']['url']; + break; + case 6: + ad['teaser_type'] = 'vast'; + ad['video_url'] = asset['video']['vasttag']; + break; + case 10: + ad['sponsor_name'] = asset['data']['value']; + break; + case 2001: + ad['expanded_content_type'] = 'embed'; + ad['expanded_summary'] = asset['data']['value']; + break; + case 2002: + ad['expanded_content_type'] = 'vast'; + ad['expanded_summary'] = asset['data']['value']; + break; + case 2003: + ad['sponsor_url'] = asset['data']['value']; + break; + case 2004: // prism + ad['content_type'] = 'prism'; + break; + case 2005: // internal_landing_page + ad['content_type'] = 'internal_landing_page'; + ad['internal_content_link'] = asset['data']['value']; + break; + case 2006: // teaser as vast + ad['teaser_type'] = 'vast'; + ad['video_url'] = asset['data']['value']; + break; + case 2007: + ad['autoexpand_content_type'] = asset['data']['value']; + break; + case 2022: // content page + ad['content_type'] = 'full_text'; + ad['full_text'] = asset['data']['value']; + break; + } + } + + ad['action_url'] = link.url; + + if (!ad['sponsor_url']) { + ad['sponsor_url'] = ad['action_url']; + } + + ad['clicktrackers'] = []; + if (link.clicktrackers) { + ad['clicktrackers'] = link.clicktrackers; + } + + ad['main_image'] = '//resize-ssp.elasticad.net/scalecrop-290x130/' + window.btoa(ad['main_image']) + '/external'; + + bid.ad = '
' + + '
' + + '
' + + ' ' + + ' ' + + '
' + + '

' + ad['title'] + '

' + + '

' + ad['content'] + ' 

' + + ' ' + + '
' + + '' + + '' + + '' + + '
'; + } + } else { + // native + bid.mediaType = 'native'; + if (bidRequest.mediaType === 'native') { + if (serverBody.native) { + let assets = serverBody.native.assets; + let link = serverBody.native.link; + + let trackers = []; + if (serverBody.native.imptrackers) { + trackers = serverBody.native.imptrackers; + } + + if (serverBody.nurl) { + trackers.push(serverBody.nurl); + } + + let native = {}; + + for (let i = 0; i < assets.length; i++) { + let asset = assets[i]; + switch (asset['id']) { + case 1: + native.title = asset['title']['text']; + break; + case 2: + native.icon = asset['img']; + break; + case 3: + native.body = asset['data']['value']; + break; + case 4: + native.image = asset['img']; + break; + case 10: + native.sponsoredBy = asset['data']['value']; + break; + } + } + native.cta = 'read more'; + if (serverBody.language) { + native.cta = 'read more'; + } + + native.clickUrl = link.url; + native.impressionTrackers = trackers; + if (link.clicktrackers) { + native.clickTrackers = link.clicktrackers; + } + + bid.qtx_native = utils.deepClone(serverBody.native); + bid.native = native; + } + } + } + bidResponses.push(bid); + } + + 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) { + const syncs = [] + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: '//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html' + }); + } + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + syncs.push({ + type: 'image', + url: serverResponses[0].body.sync[0] + }); + } + return syncs; + } +} +registerBidder(spec); diff --git a/modules/quantumBidAdapter.md b/modules/quantumBidAdapter.md new file mode 100644 index 00000000000..ac64ab371c5 --- /dev/null +++ b/modules/quantumBidAdapter.md @@ -0,0 +1,94 @@ +# Overview + +``` +Module Name: Quantum Advertising Bid Adapter +Module Type: Bidder Adapter +Maintainer: sami@elasticad.com +``` + +# Description + +Connects to Quantum's ssp for bids. + +# Sample Ad Unit: For Publishers +``` +var adUnits = [{ + code: 'quantum-adUnit-id-1', + sizes: [[300, 250]], + bids: [{ + bidder: 'quantum', + params: { + placementId: 21546 //quantum adUnit id + } + }] + },{ + code: 'quantum-native-adUnit-id-1', + sizes: [[0, 0]], + mediaTypes: 'native', + bids: [{ + bidder: 'quantum', + params: { + placementId: 21546 //quantum adUnit id + } + }] + }]; +``` + +# Ad Unit and Setup: For Testing + +``` + + + + + + + + + + ``` diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js new file mode 100644 index 00000000000..6c0773d1f7c --- /dev/null +++ b/modules/readpeakBidAdapter.js @@ -0,0 +1,245 @@ +import { logError, getTopWindowLocation, replaceAuctionPrice, getTopWindowReferrer } from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; +import { NATIVE } from 'src/mediaTypes'; + +export const ENDPOINT = '//app.readpeak.com/header/prebid'; + +const NATIVE_DEFAULTS = { + TITLE_LEN: 70, + DESCR_LEN: 120, + SPONSORED_BY_LEN: 50, + IMG_MIN: 150, + ICON_MIN: 50, + CTA_LEN: 50, +}; + +const BIDDER_CODE = 'readpeak' + +export const spec = { + + code: BIDDER_CODE, + + supportedMediaTypes: [NATIVE], + + isBidRequestValid: bid => ( + !!(bid && bid.params && bid.params.publisherId && bid.nativeParams) + ), + + buildRequests: bidRequests => { + const request = { + id: bidRequests[0].bidderRequestId, + imp: bidRequests.map(slot => impression(slot)).filter(imp => imp.native != null), + site: site(bidRequests), + app: app(bidRequests), + device: device(), + cur: config.getConfig('currency') || ['USD'], + source: { + fd: 1, + tid: bidRequests[0].transactionId, + ext: { + prebid: '$prebid.version$', + }, + }, + } + + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(request), + } + }, + + interpretResponse: (response, request) => ( + bidResponseAvailable(request, response) + ), +}; + +function bidResponseAvailable(bidRequest, bidResponse) { + const idToImpMap = {}; + const idToBidMap = {}; + if (!bidResponse['body']) { + return [] + } + bidResponse = bidResponse.body + parse(bidRequest.data).imp.forEach(imp => { + idToImpMap[imp.id] = imp; + }); + if (bidResponse) { + bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => { + idToBidMap[bid.impid] = bid; + })); + } + const bids = []; + Object.keys(idToImpMap).forEach(id => { + if (idToBidMap[id]) { + const bid = { + requestId: id, + cpm: idToBidMap[id].price, + creativeId: idToBidMap[id].crid, + ttl: 300, + netRevenue: true, + mediaType: NATIVE, + currency: bidResponse.cur, + native: nativeResponse(idToImpMap[id], idToBidMap[id]), + }; + bids.push(bid); + } + }); + return bids; +} + +function impression(slot) { + return { + id: slot.bidId, + native: nativeImpression(slot), + bidfloor: slot.params.bidfloor || 0, + bidfloorcur: slot.params.bidfloorcur || 'USD' + }; +} + +function nativeImpression(slot) { + if (slot.nativeParams) { + const assets = []; + addAsset(assets, titleAsset(1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN)); + addAsset(assets, imageAsset(2, slot.nativeParams.image, 3, slot.nativeParams.wmin || NATIVE_DEFAULTS.IMG_MIN, slot.nativeParams.hmin || NATIVE_DEFAULTS.IMG_MIN)); + addAsset(assets, dataAsset(3, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN)); + addAsset(assets, dataAsset(4, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN)); + addAsset(assets, dataAsset(5, slot.nativeParams.cta, 12, NATIVE_DEFAULTS.CTA_LEN)); + return { + request: JSON.stringify({ assets }), + ver: '1.1', + }; + } + return null; +} + +function addAsset(assets, asset) { + if (asset) { + assets.push(asset); + } +} + +function titleAsset(id, params, defaultLen) { + if (params) { + return { + id, + required: params.required ? 1 : 0, + title: { + len: params.len || defaultLen, + }, + }; + } + return null; +} + +function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { + return params ? { + id, + required: params.required ? 1 : 0, + img: { + type, + wmin: params.wmin || defaultMinWidth, + hmin: params.hmin || defaultMinHeight, + } + } : null; +} + +function dataAsset(id, params, type, defaultLen) { + return params ? { + id, + required: params.required ? 1 : 0, + data: { + type, + len: params.len || defaultLen, + } + } : null; +} + +function site(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.publisherId : '0'; + const siteId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.siteId : '0'; + const appParams = bidderRequest[0].params.app; + if (!appParams) { + return { + publisher: { + id: pubId.toString(), + domain: config.getConfig('publisherDomain'), + }, + id: siteId ? siteId.toString() : pubId.toString(), + ref: getTopWindowReferrer(), + page: config.getConfig('pageUrl') || getTopWindowLocation().href, + domain: getTopWindowLocation().hostname + } + } + return undefined; +} + +function app(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.publisherId : '0'; + const appParams = bidderRequest[0].params.app; + if (appParams) { + return { + publisher: { + id: pubId.toString(), + }, + bundle: appParams.bundle, + storeurl: appParams.storeUrl, + domain: appParams.domain, + } + } + return undefined; +} + +function device() { + return { + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + devicetype: 1 + }; +} + +function parse(rawResponse) { + try { + if (rawResponse) { + if (typeof rawResponse === 'object') { + return rawResponse + } else { + return JSON.parse(rawResponse); + } + } + } catch (ex) { + logError('readpeakBidAdapter.safeParse', 'ERROR', ex); + } + return null; +} + +function nativeResponse(imp, bid) { + if (imp && imp['native']) { + const nativeAd = parse(bid.adm); + const keys = {}; + if (nativeAd && nativeAd.assets) { + nativeAd.assets.forEach(asset => { + keys.title = asset.title ? asset.title.text : keys.title; + keys.body = asset.data && asset.id === 4 ? asset.data.value : keys.body; + keys.sponsoredBy = asset.data && asset.id === 3 ? asset.data.value : keys.sponsoredBy; + keys.image = asset.img && asset.id === 2 ? { + url: asset.img.url, + width: asset.img.w || 750, + height: asset.img.h || 500, + } : keys.image; + keys.cta = asset.data && asset.id === 5 ? asset.data.value : keys.cta; + }); + if (nativeAd.link) { + keys.clickUrl = encodeURIComponent(nativeAd.link.url); + } + const trackers = nativeAd.imptrackers || []; + trackers.unshift(replaceAuctionPrice(bid.burl, bid.price)); + keys.impressionTrackers = trackers; + return keys; + } + } + return null; +} + +registerBidder(spec); diff --git a/modules/readpeakBidAdapter.md b/modules/readpeakBidAdapter.md new file mode 100644 index 00000000000..a15767f29a7 --- /dev/null +++ b/modules/readpeakBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +Module Name: ReadPeak Bid Adapter + +Module Type: Bidder Adapter + +Maintainer: kurre.stahlberg@readpeak.com + +# Description + +Module that connects to ReadPeak's demand sources + +This adapter requires setup and approval from ReadPeak. +Please reach out to your account team or hello@readpeak.com for more information. + +# Test Parameters +```javascript + var adUnits = [{ + code: '/19968336/prebid_native_example_2', + mediaTypes: { native: { type: 'image' } }, + bids: [{ + bidder: 'readpeak', + params: { + bidfloor: 5.00, + publisherId: 'test', + siteId: 'test' + }, + }] + }]; +``` diff --git a/modules/realvuAnalyticsAdapter.js b/modules/realvuAnalyticsAdapter.js new file mode 100644 index 00000000000..7dbb309ea18 --- /dev/null +++ b/modules/realvuAnalyticsAdapter.js @@ -0,0 +1,945 @@ +// RealVu Analytics Adapter +import adapter from 'src/AnalyticsAdapter'; +import adaptermanager from 'src/adaptermanager'; +import CONSTANTS from 'src/constants.json'; + +const utils = require('src/utils.js'); + +let realvuAnalyticsAdapter = adapter({ + global: 'realvuAnalytics', + handler: 'on', + analyticsType: 'bundle' +}); +window.top1 = window; +try { + let wnd = window; + while ((window.top1 != top) && (typeof (wnd.document) != 'undefined')) { + window.top1 = wnd; + wnd = wnd.parent; + } +} catch (e) { + /* continue regardless of error */ +} +window.top1.realvu_aa_fifo = window.top1.realvu_aa_fifo || []; +window.top1.realvu_aa = window.top1.realvu_aa || { + ads: [], + x1: 0, + y1: 0, + x2: 0, + y2: 0, + t0: new Date(), + nn: 0, + frm: false, // check first if we are inside other domain iframe + msg: [], + foc: !window.top1.document.hidden, // 1-in, 0-out of focus + c: '', // owner id + sr: '', // + beacons: [], // array of beacons to collect while 'conf' is not responded + init: function () { + let z = this; + let u = navigator.userAgent; + z.device = u.match(/iPad|Tablet/gi) ? 'tablet' : u.match(/iPhone|iPod|Android|Opera Mini|IEMobile/gi) ? 'mobile' : 'desktop'; + if (typeof (z.len) == 'undefined') z.len = 0; // check, meybe too much, just make it len:0, + z.ie = navigator.appVersion.match(/MSIE/); + z.saf = (u.match(/Safari/) && !u.match(/Chrome/)); + z.ff = u.match(/Firefox/i); + z.cr = (u.match(/Chrome/)); + z.ope = window.opera; + z.fr = 0; + if (window.top1 != top) { + z.fr = 2; + if (typeof window.top1.$sf != 'undefined') { + z.fr = 1; + } + } + z.add_evt(window.top1, 'focus', function () { + window.top1.realvu_aa.foc = 1; /* window.top1.realvu_aa.log('focus',-1); */ + }); + // z.add_evt(window.top1, "scroll", function(){window.top1.realvu_aa.foc=1;window.top1.realvu_aa.log('scroll focus',-1);}); + z.add_evt(window.top1, 'blur', function () { + window.top1.realvu_aa.foc = 0; /* window.top1.realvu_aa.log('blur',-1); */ + }); + // + http://www.w3.org/TR/page-visibility/ + z.add_evt(window.top1.document, 'blur', function () { + window.top1.realvu_aa.foc = 0; /* window.top1.realvu_aa.log('blur',-1); */ + }); + z.add_evt(window.top1, 'visibilitychange', function () { + window.top1.realvu_aa.foc = !window.top1.document.hidden; + /* window.top1.realvu_aa.log('vis-ch '+window.top1.realvu_aa.foc,-1); */ + }); + // - + z.doLog = (window.top1.location.search.match(/boost_log/) || document.referrer.match(/boost_log/)) ? 1 : 0; + if (z.doLog) { + window.setTimeout(z.scr(window.top1.location.protocol + '//ac.realvu.net/realvu_aa_viz.js'), 500); + } + }, + + add_evt: function (elem, evtType, func) { + if (elem.addEventListener) { + elem.addEventListener(evtType, func, true); + } else if (elem.attachEvent) { + elem.attachEvent('on' + evtType, func); + } else { + elem['on' + evtType] = func; + } + }, + + update: function () { + let z = this; + let de = window.top1.document.documentElement; + z.x1 = window.top1.pageXOffset ? window.top1.pageXOffset : de.scrollLeft; + z.y1 = window.top1.pageYOffset ? window.top1.pageYOffset : de.scrollTop; + let w1 = window.top1.innerWidth ? window.top1.innerWidth : de.clientWidth; + let h1 = window.top1.innerHeight ? window.top1.innerHeight : de.clientHeight; + z.x2 = z.x1 + w1; + z.y2 = z.y1 + h1; + }, + brd: function (s, p) { // return a board Width, s-element, p={Top,Right,Bottom, Left} + let u; + if (window.getComputedStyle) u = window.getComputedStyle(s, null); + else u = s.style; + let a = u['border' + p + 'Width']; + return parseInt(a.length > 2 ? a.slice(0, -2) : 0); + }, + + padd: function (s, p) { // return a board Width, s-element, p={Top,Right,Bottom, Left} + let u; + if (window.getComputedStyle) u = window.getComputedStyle(s, null); + else u = s.style; + let a = u['padding' + p]; + return parseInt(a.length > 2 ? a.slice(0, -2) : 0); + }, + + viz_area: function (x1, x2, y1, y2) { // coords of Ad + if (this.fr == 1) { + try { + let iv = Math.round(100 * window.top1.$sf.ext.geom().self.iv); + return iv; + } catch (e) { + /* continue regardless of error */ + } + } + let xv1 = Math.max(x1, this.x1); + let yv1 = Math.max(y1, this.y1); + let xv2 = Math.min(x2, this.x2); + let yv2 = Math.min(y2, this.y2); + let A = Math.round(100 * ((xv2 - xv1) * (yv2 - yv1)) / ((x2 - x1) * (y2 - y1))); + return (A > 0) ? A : 0; + }, + + viz_dist: function (x1, x2, y1, y2) { // coords of Ad + let d = Math.max(0, this.x1 - x2, x1 - this.x2) + Math.max(0, this.y1 - y2, y1 - this.y2); + return d; + }, + + track: function (a, f, params) { + let z = this; + let s1 = z.tru(a, f) + params; + if (f == 'conf') { + z.scr(s1, a); + z.log(' ' + f + '', a.num); + } else { + let bk = { + s1: s1, + a: a, + f: f + }; + z.beacons.push(bk); + } + }, + + send_track: function () { + let z = this; + if (z.sr >= 'a') { // conf, send beacons + let bk = z.beacons.shift(); + while (typeof bk != 'undefined') { + bk.s1 = bk.s1.replace(/_sr=0*_/, '_sr=' + z.sr + '_'); + z.log(' ' + bk.a.riff + ' ' + bk.a.unit_id + /* " "+pin.mode+ */ ' ' + bk.a.w + 'x' + bk.a.h + '@' + bk.a.x + ',' + bk.a.y + + ' ' + bk.f + '', bk.a.num); + if (bk.a.rnd < Math.pow(10, 1 - (z.sr.charCodeAt(0) & 7))) { + z.scr(bk.s1, bk.a); + } + bk = z.beacons.shift(); + } + } + }, + + scr: function (s1, a) { + let st = document.createElement('script'); + st.async = true; + st.type = 'text/javascript'; + st.src = s1; + if (a && a.dv0 != null) { + a.dv0.appendChild(st); + } else { + let x = document.getElementsByTagName('script')[0]; + x.parentNode.insertBefore(st, x); + } + }, + + tru: function (a, f) { + let pin = a.pins[0]; + let s2 = '//ac.realvu.net/flip/3/c=' + pin.partner_id + + '_f=' + f + '_r=' + a.riff + + '_s=' + a.w + 'x' + a.h; + if (a.p) s2 += '_p=' + a.p; + s2 += '_ps=' + this.enc(a.unit_id) + // 08-Jun-15 - _p= is replaced with _ps= - p-number, ps-string + '_dv=' + this.device + + // + '_a=' + this.enc(a.a) + '_d=' + pin.mode + + '_sr=' + this.sr + + '_h=' + this.enc(a.ru) + '?'; + return s2.replace(/%/g, '!'); + }, + + enc: function (s1) { + // return escape(s1).replace(/[0-9a-f]{5,}/gi,'RANDOM').replace(/\*/g, "%2A").replace(/_/g, "%5F").replace(/\+/g, + return escape(s1).replace(/\*/g, '%2A').replace(/_/g, '%5F').replace(/\+/g, + '%2B').replace(/\./g, '%2E').replace(/\x2F/g, '%2F'); + }, + + findPosG: function (adi) { + let t = this; + let ad = adi; + let xp = 0; + let yp = 0; + let dc = adi.ownerDocument; + let wnd = dc.defaultView || dc.parentWindow; + + try { + while (ad != null && typeof (ad) != 'undefined') { + if (ad.getBoundingClientRect) { // Internet Explorer, Firefox 3+, Google Chrome, Opera 9.5+, Safari 4+ + let r = ad.getBoundingClientRect(); + xp += r.left; // +sL; + yp += r.top; // +sT; + if (wnd == window.top1) { + xp += t.x1; + yp += t.y1; + } + } else { + if (ad.tagName == 'IFRAME') { + xp += t.brd(ad, 'Left'); + yp += t.brd(ad, 'Top'); + } + xp += ad.offsetLeft; + yp += ad.offsetTop; + + let op = ad.offsetParent; + let pn = ad.parentNode; + let opf = ad; + while (opf != null) { + let cs = window.getComputedStyle(opf, null); + if (cs.position == 'fixed') { + if (cs.top) yp += parseInt(cs.top) + this.y1; + } + if (opf == op) break; + opf = opf.parentNode; + } + while (op != null && typeof (op) != 'undefined') { + xp += op.offsetLeft; + yp += op.offsetTop; + let ptn = op.tagName; + if (t.cr || t.saf || (t.ff && ptn == 'TD')) { + xp += t.brd(op, 'Left'); + yp += t.brd(op, 'Top'); + } + if (ad.tagName != 'IFRAME' && op != document.body && op != document.documentElement) { + xp -= op.scrollLeft; + yp -= op.scrollTop; + } + if (!t.ie) { + while (op != pn && pn != null) { + xp -= pn.scrollLeft; + yp -= pn.scrollTop; + if (t.ff_o) { + xp += t.brd(pn, 'Left'); + yp += t.brd(pn, 'Top'); + } + pn = pn.parentNode; + } + } + pn = pn.parentNode; + op = op.offsetParent; + } + } + if (this.fr) break; // inside different domain iframe or sf + ad = wnd.frameElement; // in case Ad is allocated inside iframe here we go up + wnd = wnd.parent; + } + } catch (e) { + /* continue regardless of error */ + } + let q = { + 'x': Math.round(xp), + 'y': Math.round(yp) + }; + return q; + }, + + poll: function () { + let fifo = window.top1.realvu_aa_fifo; + while (fifo.length > 0) { + (fifo.shift())(); + } + let z = this; + z.update(); + let now = new Date(); + if (typeof (z.ptm) == 'undefined') { + z.ptm = now; + } + let dvz = now - z.ptm; + z.ptm = now; + for (let i = 0; i < z.len; i++) { + let a = z.ads[i]; + let restored = false; + if (a.div == null) { // ad unit is not found yet + let adobj = document.getElementById(a.pins[0].unit_id); + if (adobj == null) { + restored = z.readPos(a); + if (!restored) continue; // do nothing if not found + } else { + z.bind_obj(a, adobj); + z.log('{m}"' + a.unit_id + '" is bound', a.num); + } + } + if (!restored) { + a.target = z.questA(a.div); + let target = (a.target !== null) ? a.target : a.div; + a.box.w = Math.max(target.offsetWidth, a.w); + a.box.h = Math.max(target.offsetHeight, a.h); + let q = z.findPosG(target); + let pad = {}; + pad.t = z.padd(target, 'Top'); + pad.l = z.padd(target, 'Left'); + pad.r = z.padd(target, 'Right'); + pad.b = z.padd(target, 'Bottom'); + let ax = q.x + pad.l; + let ay = q.y + pad.t; + a.box.x = ax; + a.box.y = ay; + if (a.box.w > a.w && a.box.w > 1) { + ax += (a.box.w - a.w - pad.l - pad.r) / 2; + } + if (a.box.h > a.h && a.box.h > 1) { + ay += (a.box.h - a.h - pad.t - pad.b) / 2; + } + if ((ax > 0 && ay > 0) && (a.x != ax || a.y != ay)) { + a.x = ax; + a.y = ay; + z.writePos(a); + } + } + let vtr = ((a.box.w * a.box.h) < 242500) ? 49 : 29; // treashfold more then 49% and more then 29% for "oversized" + if (a.pins[0].edge) { + vtr = a.pins[0].edge - 1; // override default edge 50% (>49) + } + a.vz = z.viz_area(a.box.x, a.box.x + a.box.w, a.box.y, a.box.y + a.box.h); + a.r = (z.fr > 1 ? 'frame' : (((a.vz > vtr) && z.foc) ? 'yes' : 'no')); // f-frame, y-yes in view,n-not in view + if (a.y < 0) { + a.r = 'out'; // if the unit intentionaly moved out, count it as out. + } + if (a.vz > vtr && z.foc) { + a.vt += dvz; // real dt counter in milliseconds, because of poll() can be called irregularly + a.vtu += dvz; + } + // now process every pin + let plen = a.pins.length; + for (let j = 0; j < plen; j++) { + let pin = a.pins[j]; + if (pin.state <= 1) { + let dist = z.viz_dist(a.x, a.x + a.w, a.y, a.y + a.h); + let near = (pin.dist != null && dist <= pin.dist); + // apply "near" rule for ad call only + a.r = (z.fr > 1) ? 'frame' : (((a.vz > vtr) && z.foc) ? 'yes' : 'no'); + if (near && a.r == 'no') { + a.r = 'yes'; + } + if (a.riff === '') { + a.riff = a.r; + let vr_score = z.score(a, 'v:r'); + if (vr_score != null) { + if (a.r == 'no' && vr_score > 75) { + a.riff = 'yes'; + } + } + let vv0_score = z.score(a, 'v:v0'); + if (vv0_score != null) { + if (a.r == 'yes' && vv0_score < (30 + 25 * Math.random())) { + a.riff = 'no'; + } + } + } + if ((pin.mode == 'kvp' || pin.mode == 'tx2') || (((a.vz > vtr) || near) && ((pin.mode == 'in-view' || pin.mode == 'video')))) { + z.show(a, pin); // in-view or flip show immediately if initial realvu=yes, or delay is over + } + } + if (pin.state == 2) { + a.target = z.questA(a.div); + if (a.target != null) { + pin.state = 3; + dvz = 0; + a.vt = 0; + // @if NODE_ENV='debug' + let now = new Date(); + let msg = (now.getTime() - time0) / 1000 + ' RENDERED ' + a.unit_id; + utils.logMessage(msg); + // @endif + let rpt = z.bids_rpt(a, true); + z.track(a, 'rend', rpt); + z.incrMem(a, 'r', 'v:r'); + } + } + if (pin.state > 2) { + let tmin = (pin.mode == 'video') ? 2E3 : 1E3; // mrc min view time + if (a.vz > vtr) { + pin.vt += dvz; // real dt counter in milliseconds, because of poll() can be called irregularly + if (pin.state == 3) { + pin.state = 4; + z.incrMem(a, 'r', 'v:v0'); + } + if (pin.state == 4 && pin.vt >= tmin) { + pin.state = 5; + let rpt = z.bids_rpt(a, true); + z.track(a, 'view', rpt); + z.incrMem(a, 'v', 'v:r'); + z.incrMem(a, 'v', 'v:v0'); + } + if (pin.state == 5 && pin.vt >= 5 * tmin) { + pin.state = 6; + let rpt = z.bids_rpt(a, true); + z.track(a, 'view2', rpt); + } + } else if (pin.vt < tmin) { + pin.vt = 0; // reset to track continuous 1 sec + } + } + if (pin.state >= 2 && pin.mode === 'tx2' && + ((a.vtu > pin.rotate) || (pin.delay > 0 && a.vtu > pin.delay && a.riff === 'no' && a.ncall < 2)) && pin.tx2n > 0) { + // flip or rotate + pin.tx2n--; + pin.state = 1; + a.vtu = 0; + a.target = null; + } + } + } + this.send_track(); + }, + + questA: function (a) { // look for the visible object of ad_sizes size + // returns the object or null + if (a == null) return a; + if (a.nodeType == Node.TEXT_NODE) { + let dc = a.ownerDocument; + let wnd = dc.defaultView || dc.parentWindow; + let par = a.parentNode; + if (wnd == wnd.top) { + return par; + } else { + return par.offsetParent; + } + } + let not_friendly = false; + let ain = null; + let tn = a.tagName; + if (tn == 'HEAD' || tn == 'SCRIPT') return null; + if (tn == 'IFRAME') { + ain = this.doc(a); + if (ain == null) { + not_friendly = true; + } else { + a = ain; + tn = a.tagName; + } + } + if (not_friendly || tn == 'OBJECT' || tn == 'IMG' || tn == 'EMBED' || tn == 'SVG' || tn == 'CANVAS' || + (tn == 'DIV' && a.style.backgroundImage)) { + let w1 = a.offsetWidth; + let h1 = a.offsetHeight; + if (w1 > 33 && h1 > 33 && a.style.display != 'none') return a; + } + if (a.hasChildNodes()) { + let b = a.firstChild; + while (b != null) { + let c = this.questA(b); + if (c != null) return c; + b = b.nextSibling; + } + } + return null; + }, + + doc: function(f) { // return document of f-iframe, keep here "n" as a parameter because of call from setTimeout() + let d = null; + try { + if (f.contentDocument) d = f.contentDocument; // DOM + else if (f.contentWindow) d = f.contentWindow.document; // IE + } catch (e) { + /* continue regardless of error */ + } + return d; + }, + + bind_obj: function (a, adobj) { + a.div = adobj; + a.target = null; // initially null, found ad when served + a.unit_id = adobj.id; // placement id or name + a.w = adobj.offsetWidth || 1; // width, min 1 + a.h = adobj.offsetHeight || 1; // height, min 1 + }, + add: function (wnd1, p) { // p - realvu unit id + let a = { + num: this.len, + x: 0, + y: 0, + box: { + x: 0, + y: 0, + h: 1, + w: 1 + }, // measured ad box + p: p, + state: 0, // 0-init, (1-loaded,2-rendered,3-viewed) + delay: 0, // delay in msec to show ad after gets in view + vt: 0, // total view time + vtu: 0, // view time to update and mem + a: '', // ad_placement id + wnd: wnd1, + div: null, + pins: [], + frm: null, // it will be frame when "show" + riff: '', // r to report + rnd: Math.random(), + ncall: 0, // a callback number + rq_bids: [], // rq bids of registered partners + bids: [] // array of bids + }; + a.ru = window.top1.location.hostname; + window.top1.realvu_aa.ads[this.len++] = a; + return a; + }, + + fmt: function (a, pin) { + return { + 'realvu': a.r, + 'riff': a.riff, + 'area': a.vz, + 'ncall': a.ncall, + 'n': a.num, + 'id': a.unit_id, + 'pin': pin + }; + }, + + show: function (a, pin) { + pin.state = 2; // 2-published + pin.vt = 0; // reset view time counter + if (pin.size) { + let asz = this.setSize(pin.size); + if (asz != null) { + a.w = asz.w; + a.h = asz.h; + } + } + if (typeof pin.callback != 'undefined') { + pin.callback(this.fmt(a, pin)); + } + a.ncall++; + this.track(a, 'show', ''); + }, + + check: function (p1) { + let pin = { + dist: 150, + state: 0, + tx2n: 7 + }; // if dist is set trigger ad when distance < pin.dist + for (let attr in p1) { + if (p1.hasOwnProperty(attr)) { + if ((attr == 'ad_sizes') && (typeof (p1[attr]) == 'string')) { + pin[attr] = p1[attr].split(','); + } else if (attr == 'edge') { + try { + let ed = parseInt(p1[attr]); + if (ed > 0 && ed < 251) pin[attr] = ed; + } catch (e) { + /* continue regardless of error */ + } + } else { + pin[attr] = p1[attr]; + } + } + } + let a = null; + let z = this; + try { + // not to track the same object more than one time + for (let i = 0; i < z.len; i++) { + // if (z.ads[i].div == adobj) { a = z.ads[i]; break; } + if (z.ads[i].unit_id == pin.unit_id) { + a = z.ads[i]; + break; + } + } + pin.wnd = pin.wnd || window; + if (a == null) { + a = z.add(pin.wnd, pin.p); + a.unit_id = pin.unit_id; + let adobj = (pin.unit) ? pin.unit : document.getElementById(a.unit_id); + if (adobj != null) { + z.bind_obj(a, adobj); + } else { + z.log('{w}"' + pin.unit_id + '" not found', a.num); + } + if (pin.size) { + let asz = z.setSize(pin.size); + if (asz != null) { + a.w = asz.w; + a.h = asz.h; + } + } + pin.delay = pin.delay || 0; // delay in msec + if (typeof pin.mode == 'undefined') { + if ((typeof pin.callback != 'undefined') || (typeof pin.content != 'undefined')) { + pin.mode = (pin.delay > 0) ? 'tx2' : 'in-view'; + } else { + pin.mode = 'kvp'; + } + // delays are for views only + } + pin.vt = 0; // view time + pin.state = 0; + a.pins.push(pin); + } + if (this.sr === '') { + z.track(a, 'conf', ''); + this.sr = '0'; + } + this.poll(); + return a; + } catch (e) { + z.log(e.message, -1); + return { + r: 'err' + }; + } + }, + + setSize: function (sa) { + let sb = sa; + try { + if (typeof (sa) == 'string') sb = sa.split('x'); // pin.size is a string WWWxHHH or array + else if (Array.isArray(sa)) { + let mm = 4; + while (--mm > 0 && Array.isArray(sa[0]) && Array.isArray(sa[0][0])) { + sa = sa[0]; + } + for (let m = 0; m < sa.length; m++) { + if (Array.isArray(sa[m])) { + sb = sa[m]; // if size is [][] + let s = sb[0] + 'x' + sb[1]; + if (s == '300x250' || s == '728x90' || s == '320x50' || s == '970x90') { + break; // use most popular sizes + } + } else if (sa.length > 1) { + sb = sa; + } + } + } + let w1 = parseInt(sb[0]); + let h1 = parseInt(sb[1]); + return { + w: w1, + h: h1 + }; + } catch (e) { + /* continue regardless of error */ + } + return null; + }, + // API functions + addUnitById: function (partner_id, unit_id, callback, delay) { + let p1 = partner_id; + if (typeof (p1) == 'string') { + p1 = { + partner_id: partner_id, + unit_id: unit_id, + callback: callback, + delay: delay + }; + } + let a = window.top1.realvu_aa.check(p1); + return a.r; + }, + + checkBidIn: function(partnerId, args, b) { // process a bid from hb + // b==true - add/update, b==false - update only + if (args.cpm == 0) return; // collect only bids submitted + const boost = window.top1.realvu_aa; + let push_bid = false; + let adi = null; + if (!b) { // update only if already checked in by xyzBidAdapter + for (let i = 0; i < boost.ads.length; i++) { + adi = boost.ads[i]; + if (adi.unit_id == args.adUnitCode) { + push_bid = true; + break; + } + } + } else { + push_bid = true; + adi = window.top1.realvu_aa.check({ + unit_id: args.adUnitCode, + size: args.size, + partner_id: partnerId + }); + } + if (push_bid) { + let pb = { + bidder: args.bidder, + cpm: args.cpm, + size: args.size, + adId: args.adId, + requestId: args.requestId, + crid: '', + ttr: args.timeToRespond, + winner: 0 + }; + if (args.creative_id) { + pb.crid = args.creative_id; + } + adi.bids.push(pb); + } + }, + + checkBidWon: function(partnerId, args, b) { + // b==true - add/update, b==false - update only + const z = this; + const unit_id = args.adUnitCode; + for (let i = 0; i < z.ads.length; i++) { + let adi = z.ads[i]; + if (adi.unit_id == unit_id) { + for (let j = 0; j < adi.bids.length; j++) { + let bj = adi.bids[j]; + if (bj.adId == args.adId) { + bj.winner = 1; + break; + } + } + let rpt = z.bids_rpt(adi, false); + z.track(adi, 'win', rpt); + break; + } + } + }, + + bids_rpt: function(a, wo) { // a-unit, wo=true - WinnerOnly + let rpt = ''; + for (let i = 0; i < a.bids.length; i++) { + let g = a.bids[i]; + if (wo && !g.winner) continue; + rpt += '&bdr=' + g.bidder + '&cpm=' + g.cpm + '&vi=' + a.riff + + '&gw=' + g.winner + '&crt=' + g.crid + '&ttr=' + g.ttr; + // append bid partner_id if any + let pid = ''; + for (let j = 0; j < a.rq_bids.length; j++) { + let rqb = a.rq_bids[j]; + if (rqb.adId == g.adId) { + pid = rqb.partner_id; + break; + } + } + rpt += '&bc=' + pid; + } + return rpt; + }, + + getStatusById: function (unit_id) { // return status object + for (let i = 0; i < this.ads.length; i++) { + let adi = this.ads[i]; + if (adi.unit_id == unit_id) return this.fmt(adi); + } + return null; + }, + + log: function (m1, i) { + if (this.doLog) { + this.msg.push({ + dt: new Date() - this.t0, + s: 'U' + (i + 1) + m1 + }); + } + }, + + keyPos: function (a) { + if (a.pins[0].unit_id) { + let level = 'L' + (window.top1.location.pathname.match(/\//g) || []).length; + return 'realvu.' + level + '.' + a.pins[0].unit_id.replace(/[0-9]{5,}/gi, 'RANDOM'); + } + }, + + writePos: function (a) { + try { + let v = a.x + ',' + a.y + ',' + a.w + ',' + a.h; + localStorage.setItem(this.keyPos(a), v); + } catch (ex) { + /* continue regardless of error */ + } + }, + + readPos: function (a) { + try { + let s = localStorage.getItem(this.keyPos(a)); + if (s) { + let v = s.split(','); + a.x = parseInt(v[0], 10); + a.y = parseInt(v[1], 10); + a.w = parseInt(v[2], 10); + a.h = parseInt(v[3], 10); + a.box = {x: a.x, y: a.y, w: a.w, h: a.h}; + return true; + } + } catch (ex) { + /* do nothing */ + } + return false; + }, + + incrMem: function(a, evt, name) { + try { + let k1 = this.keyPos(a) + '.' + name; + let vmem = localStorage.getItem(k1); + if (vmem == null) vmem = '1:3'; + let vr = vmem.split(':'); + let nv = parseInt(vr[0], 10); + let nr = parseInt(vr[1], 10); + if (evt == 'r') { + nr <<= 1; + nr |= 1; + nv <<= 1; + } + if (evt == 'v') { + nv |= 1; + } + localStorage.setItem(k1, nv + ':' + nr); + } catch (ex) { + /* do nothing */ + } + }, + + score: function (a, name) { + try { + let vstr = localStorage.getItem(this.keyPos(a) + '.' + name); + if (vstr != null) { + let vr = vstr.split(':'); + let nv = parseInt(vr[0], 10); + let nr = parseInt(vr[1], 10); + let sv = 0; + let sr = 0; + for (nr &= 0x3FF; nr > 0; nr >>>= 1, nv >>>= 1) { // count 10 deliveries + if (nr & 0x1) sr++; + if (nv & 0x1) sv++; + } + return Math.round(sv * 100 / sr); + } + } catch (ex) { + /* do nothing */ + } + return null; + } +}; + +if (typeof (window.top1.boost_poll) == 'undefined') { + window.top1.realvu_aa.init(); + window.top1.boost_poll = setInterval(function () { + window.top1 && window.top1.realvu_aa && window.top1.realvu_aa.poll(); + }, 20); +} + +let _options = {}; + +realvuAnalyticsAdapter.originEnableAnalytics = realvuAnalyticsAdapter.enableAnalytics; + +realvuAnalyticsAdapter.enableAnalytics = function (config) { + _options = config.options; + if (typeof (_options.partnerId) == 'undefined' || _options.partnerId == '') { + utils.logError('Missed realvu.com partnerId parameter', 101, 'Missed partnerId parameter'); + } + realvuAnalyticsAdapter.originEnableAnalytics(config); + return _options.partnerId; +}; + +const time0 = (new Date()).getTime(); + +realvuAnalyticsAdapter.track = function ({eventType, args}) { + // @if NODE_ENV='debug' + let msg = ''; + let now = new Date(); + msg += (now.getTime() - time0) / 1000 + ' eventType=' + eventType; + if (typeof (args) != 'undefined') { + msg += ', args.bidder=' + args.bidder + ' args.adUnitCode=' + args.adUnitCode + + ' args.adId=' + args.adId + + ' args.cpm=' + args.cpm + + ' creativei_id=' + args.creative_id; + } + // msg += '\nargs=' + JSON.stringify(args) + '
'; + utils.logMessage(msg); + // @endif + + const boost = window.top1.realvu_aa; + let b = false; // false - update only, true - add if not checked in yet + let partnerId = null; + if (_options && _options.partnerId && args) { + partnerId = _options.partnerId; + let code = args.adUnitCode; + b = _options.regAllUnits; + if (!b && _options.unitIds) { + for (let j = 0; j < _options.unitIds.length; j++) { + if (code === _options.unitIds[j]) { + b = true; + break; + } + } + } + } + if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { + boost.checkBidIn(partnerId, args, b); + } else if (eventType === CONSTANTS.EVENTS.BID_WON) { + boost.checkBidWon(partnerId, args, b); + } +}; + +// xyzBidAdapter calls checkin() to obtain "yes/no" viewability +realvuAnalyticsAdapter.checkIn = function (bid, partnerId) { + // find (or add if not registered yet) the unit in boost + if (typeof (partnerId) == 'undefined' || partnerId == '') { + utils.logError('Missed realvu.com partnerId parameter', 102, 'Missed partnerId parameter'); + } + let a = window.top1.realvu_aa.check({ + unit_id: bid.adUnitCode, + size: bid.sizes, + partner_id: partnerId + }); + a.rq_bids.push({ + bidder: bid.bidder, + adId: bid.bidId, + partner_id: partnerId + }); + return a.riff; +}; + +realvuAnalyticsAdapter.isInView = function (adUnitCode) { + let r = 'NA'; + let s = window.top1.realvu_aa.getStatusById(adUnitCode); + if (s) { + r = s.realvu; + } + return r; +}; + +adaptermanager.registerAnalyticsAdapter({ + adapter: realvuAnalyticsAdapter, + code: 'realvuAnalytics' +}); + +module.exports = realvuAnalyticsAdapter; diff --git a/modules/realvuAnalyticsAdapter.md b/modules/realvuAnalyticsAdapter.md new file mode 100644 index 00000000000..c534f78bc94 --- /dev/null +++ b/modules/realvuAnalyticsAdapter.md @@ -0,0 +1,9 @@ +# Overview + +Module Name: RealVu Analytics Adapter +Module Type: Analytics Adapter +Maintainer: it@realvu.com + +# Description + +Analytics adapter for realvu.com. Contact support@realvu.com for information. diff --git a/modules/rhythmoneBidAdapter.js b/modules/rhythmoneBidAdapter.js index 6143c081562..6b4b5e7d4e6 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -1,286 +1,258 @@ -import {ajax} from 'src/ajax'; -import adaptermanager from 'src/adaptermanager'; -import { config } from 'src/config'; - -const bidmanager = require('src/bidmanager.js'); -const bidfactory = require('src/bidfactory.js'); -const CONSTANTS = require('src/constants.json'); - -function RhythmoneAdapter (bidManager, global, loader) { - const version = '0.9.0.0'; - let defaultZone = '1r'; - let defaultPath = 'mvo'; - let debug = false; - const placementCodes = {}; - let loadStart; - let configuredPlacements = []; - const fat = /(^v|(\.0)+$)/gi; - - if (typeof global === 'undefined') { global = window; } - - if (typeof bidManager === 'undefined') { bidManager = bidmanager; } - - if (typeof loader === 'undefined') { loader = ajax; } - - function applyMacros(txt, values) { - return txt.replace(/\{([^\}]+)\}/g, function(match) { - var v = values[match.replace(/[\{\}]/g, '').toLowerCase()]; - if (typeof v !== 'undefined') return v; - return match; - }); - } - - function load(bidParams, url, callback) { - loader(url, function(responseText, response) { - if (response.status === 200) { - callback(200, 'success', response.responseText); - } else { - callback(-1, 'http error ' + response.status, response.responseText); - } - }, false, {method: 'GET', withCredentials: true}); - } - - function flashInstalled() { - const n = global.navigator; - const p = n.plugins; - const m = n.mimeTypes; - const t = 'application/x-shockwave-flash'; - const x = global.ActiveXObject; - - if (p && - p['Shockwave Flash'] && - m && - m[t] && - m[t].enabledPlugin) { return true; } - - if (x) { - try { if ((new global.ActiveXObject('ShockwaveFlash.ShockwaveFlash'))) return true; } catch (e) {} - } - - return false; - } - - var bidderCode = 'rhythmone'; - - function attempt(valueFunction, defaultValue) { - try { - return valueFunction(); - } catch (ex) {} - return defaultValue; - } - - function logToConsole(txt) { - if (debug) { console.log(txt); } - } - - function getBidParameters(bids) { - for (var i = 0; i < bids.length; i++) { - if (typeof bids[i].params === 'object' && bids[i].params.placementId) { return bids[i].params; } - } - return null; - } - - function noBids(params) { - for (var i = 0; i < params.bids.length; i++) { - if (params.bids[i].success !== 1) { - logToConsole('registering nobid for slot ' + params.bids[i].placementCode); - var bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); - bid.bidderCode = bidderCode; - bidmanager.addBidResponse(params.bids[i].placementCode, bid); - } - } - } +'use strict'; - function getRMPURL(bidParams, bids) { - let endpoint = '//tag.1rx.io/rmp/{placementId}/0/{path}?z={zone}'; - const query = []; - - if (typeof bidParams.endpoint === 'string') { endpoint = bidParams.endpoint; } - - if (typeof bidParams.zone === 'string') { defaultZone = bidParams.zone; } - - if (typeof bidParams.path === 'string') { defaultPath = bidParams.path; } - - if (bidParams.debug === true) { debug = true; } - - if (bidParams.trace === true) { query.push('trace=true'); } - - endpoint = applyMacros(endpoint, { - placementid: bidParams.placementId, - zone: defaultZone, - path: defaultPath - }); - - function p(k, v) { - if (v instanceof Array) { v = v.join(','); } - if (typeof v !== 'undefined') { query.push(encodeURIComponent(k) + '=' + encodeURIComponent(v)); } - } - - p('domain', attempt(function() { - var d = global.document.location.ancestorOrigins; - if (d && d.length > 0) { return d[d.length - 1]; } - return global.top.document.location.hostname; // try/catch is in the attempt function - }, '')); - p('title', attempt(function() { return global.top.document.title; }, '')); // try/catch is in the attempt function - p('url', attempt(function() { - var l; - // try/catch is in the attempt function - try { - l = global.top.document.location.href.toString(); - } catch (ex) { - l = global.document.location.href.toString(); - } - return l; - }, '')); - p('dsh', (global.screen ? global.screen.height : '')); - p('dsw', (global.screen ? global.screen.width : '')); - p('tz', (new Date()).getTimezoneOffset()); - p('dtype', ((/(ios|ipod|ipad|iphone|android)/i).test(global.navigator.userAgent) ? 1 : ((/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(global.navigator.userAgent) ? 3 : 2))); - p('flash', (flashInstalled() ? 1 : 0)); - - const heights = []; - const widths = []; - const floors = []; - const mediaTypes = []; - let i = 0; +import {registerBidder} from 'src/adapters/bidderFactory'; +import { BANNER, VIDEO } from 'src/mediaTypes'; - configuredPlacements = []; +function RhythmOneBidAdapter() { + this.code = 'rhythmone'; + this.supportedMediaTypes = [VIDEO, BANNER]; - p('hbv', global.$$PREBID_GLOBAL$$.version.replace(fat, '') + ',' + version.replace(fat, '')); - - for (; i < bids.length; i++) { - const th = []; - const tw = []; + this.isBidRequestValid = function (bid) { + return true; + }; - if (bids[i].sizes.length > 0 && typeof bids[i].sizes[0] === 'number') { bids[i].sizes = [bids[i].sizes]; } + this.getUserSyncs = function (syncOptions) { + let slots = []; + let placementIds = []; - for (var j = 0; j < bids[i].sizes.length; j++) { - tw.push(bids[i].sizes[j][0]); - th.push(bids[i].sizes[j][1]); - } - configuredPlacements.push(bids[i].placementCode); - heights.push(th.join('|')); - widths.push(tw.join('|')); - mediaTypes.push(((/video/i).test(bids[i].mediaType) ? 'v' : 'd')); - floors.push(0); + for (let k in slotsToBids) { + slots.push(k); + placementIds.push(getFirstParam('placementId', [slotsToBids[k]])); } - p('imp', configuredPlacements); - p('w', widths); - p('h', heights); - p('floor', floors); - p('t', mediaTypes); - - endpoint += '&' + query.join('&'); - - return endpoint; - } - - function sendAuditBeacon(placementId) { - const data = { + let data = { doc_version: 1, doc_type: 'Prebid Audit', - placement_id: placementId + placement_id: placementIds.join(',').replace(/[,]+/g, ',').replace(/^,|,$/g, '') }; - const ao = document.location.ancestorOrigins; - const q = []; - const u = '//hbevents.1rx.io/audit?'; - const i = new Image(); + let w = typeof (window) !== 'undefined' ? window : {document: {location: {href: ''}}}; + let ao = w.document.location.ancestorOrigins; + let q = []; + let u = '//hbevents.1rx.io/audit?'; if (ao && ao.length > 0) { data.ancestor_origins = ao[ao.length - 1]; } - data.popped = window.opener !== null ? 1 : 0; - data.framed = window.top === window ? 0 : 1; + data.popped = w.opener !== null ? 1 : 0; + data.framed = w.top === w ? 0 : 1; try { - data.url = window.top.document.location.href.toString(); + data.url = w.top.document.location.href.toString(); } catch (ex) { - data.url = window.document.location.href.toString(); + data.url = w.document.location.href.toString(); } - var prebid_instance = global.$$PREBID_GLOBAL$$; + try { + data.prebid_version = '$prebid.version$'; + data.prebid_timeout = config.getConfig('bidderTimeout'); + } catch (ex) { } - data.prebid_version = prebid_instance.version.replace(fat, ''); - data.response_ms = (new Date()).getTime() - loadStart; - data.placement_codes = configuredPlacements.join(','); + data.response_ms = Date.now() - loadStart; + data.placement_codes = slots.join(','); data.bidder_version = version; - data.prebid_timeout = prebid_instance.cbTimeout || config.getConfig('bidderTimeout'); - for (var k in data) { + for (let k in data) { q.push(encodeURIComponent(k) + '=' + encodeURIComponent((typeof data[k] === 'object' ? JSON.stringify(data[k]) : data[k]))); } q.sort(); - i.src = u + q.join('&'); - } - this.callBids = function(params) { - const slotMap = {}; - const bidParams = getBidParameters(params.bids); + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: u + q.join('&') + }]; + } + }; - debug = (bidParams !== null && bidParams.debug === true); + function getFirstParam(key, validBidRequests) { + for (let i = 0; i < validBidRequests.length; i++) { + if (validBidRequests[i].params && validBidRequests[i].params[key]) { + return validBidRequests[i].params[key]; + } + } + } - if (bidParams === null) { - noBids(params); - return; + let slotsToBids = {}; + let that = this; + let version = '1.0.0.0'; + let loadStart = Date.now(); + + this.buildRequests = function (BRs) { + let fallbackPlacementId = getFirstParam('placementId', BRs); + if (fallbackPlacementId === undefined || BRs.length < 1) { + return []; } - for (var i = 0; i < params.bids.length; i++) { slotMap[params.bids[i].placementCode] = params.bids[i]; } + loadStart = Date.now(); + slotsToBids = {}; + + let query = []; + let w = (typeof window !== 'undefined' ? window : {}); - loadStart = (new Date()).getTime(); - load(bidParams, getRMPURL(bidParams, params.bids), function(code, msg, txt) { - // send quality control beacon here - sendAuditBeacon(bidParams.placementId); + function p(k, v, d) { + if (v instanceof Array) { v = v.join((d || ',')); } + if (typeof v !== 'undefined') { query.push(encodeURIComponent(k) + '=' + encodeURIComponent(v)); } + } - logToConsole('response text: ' + txt); + function attempt(valueFunction, defaultValue) { + try { + return valueFunction(); + } catch (ex) { } + return defaultValue; + } - if (code !== -1) { - try { - const result = JSON.parse(txt); - const registerBid = function registerBid(bid) { - slotMap[bid.impid].success = 1; + p('domain', attempt(function() { + var d = w.document.location.ancestorOrigins; + if (d && d.length > 0) { + return d[d.length - 1]; + } + return w.top.document.location.hostname; // try/catch is in the attempt function + }, '')); + p('url', attempt(function() { + var l; + // try/catch is in the attempt function + try { + l = w.top.document.location.href.toString(); + } catch (ex) { + l = w.document.location.href.toString(); + } + return l; + }, '')); - const pbResponse = bidfactory.createBid(CONSTANTS.STATUS.GOOD); - const placementCode = slotMap[bid.impid].placementCode; + function getRMPUrl() { + let url = getFirstParam('endpoint', BRs) || '//tag.1rx.io/rmp/{placementId}/0/{path}?z={zone}'; + let defaultZone = getFirstParam('zone', BRs) || '1r'; + let defaultPath = getFirstParam('path', BRs) || 'mvo'; + + url = url.replace(/\{placementId\}/i, fallbackPlacementId); + url = url.replace(/\{zone\}/i, defaultZone); + url = url.replace(/\{path\}/i, defaultPath); + + p('title', attempt(function() { return w.top.document.title; }, '')); // try/catch is in the attempt function + p('dsh', (w.screen ? w.screen.height : '')); + p('dsw', (w.screen ? w.screen.width : '')); + p('tz', (new Date()).getTimezoneOffset()); + p('dtype', ((/(ios|ipod|ipad|iphone|android)/i).test(w.navigator.userAgent) ? 1 : ((/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(w.navigator.userAgent) ? 3 : 2))); + p('flash', attempt(function() { + let n = w.navigator; + let p = n.plugins; + let m = n.mimeTypes; + let t = 'application/x-shockwave-flash'; + let x = w.ActiveXObject; + + if (p && + p['Shockwave Flash'] && + m && + m[t] && + m[t].enabledPlugin) { + return 1; + } + + if (x) { + try { + if ((new w.ActiveXObject('ShockwaveFlash.ShockwaveFlash'))) { + return 1; + } + } catch (e) { } + } + + return 0; + }, 0)); + + let heights = []; + let widths = []; + let floors = []; + let mediaTypes = []; + let i = 0; + let configuredPlacements = []; + let fat = /(^v|(\.0)+$)/gi; + + p('hbv', w.$$PREBID_GLOBAL$$.version.replace(fat, '') + ',' + version.replace(fat, '')); + + for (; i < BRs.length; i++) { + let th = []; + let tw = []; + let params = BRs[i].params || {}; + + slotsToBids[BRs[i].adUnitCode || BRs[i].placementCode] = BRs[i]; + + if (BRs[i].sizes.length > 0 && typeof BRs[i].sizes[0] === 'number') { + BRs[i].sizes = [BRs[i].sizes]; + } + + for (let j = 0; j < BRs[i].sizes.length; j++) { + tw.push(BRs[i].sizes[j][0]); + th.push(BRs[i].sizes[j][1]); + } + configuredPlacements.push(BRs[i].adUnitCode || BRs[i].placementCode); + heights.push(th.join('|')); + widths.push(tw.join('|')); + mediaTypes.push((BRs[i].mediaTypes && BRs[i].mediaTypes.video ? 'v' : 'd')); + floors.push(params.floor || 0); + } - placementCodes[placementCode] = false; + p('imp', configuredPlacements); + p('w', widths); + p('h', heights); + p('floor', floors); + p('t', mediaTypes); - pbResponse.bidderCode = bidderCode; - pbResponse.cpm = parseFloat(bid.price); - pbResponse.width = bid.w; - pbResponse.height = bid.h; + url += '&' + query.join('&') + '&'; - if ((/video/i).test(slotMap[bid.impid].mediaType)) { - pbResponse.mediaType = 'video'; - pbResponse.vastUrl = bid.nurl; - pbResponse.descriptionUrl = bid.nurl; - } else { pbResponse.ad = bid.adm; } + return url; + } - logToConsole('registering bid ' + placementCode + ' ' + JSON.stringify(pbResponse)); + return [{ + method: 'GET', + url: getRMPUrl() + }]; + }; - bidManager.addBidResponse(placementCode, pbResponse); - }; + this.interpretResponse = function (serverResponse) { + let responses = serverResponse.body || []; + let bids = []; + let i = 0; - for (i = 0; result.seatbid && i < result.seatbid.length; i++) { - for (var j = 0; result.seatbid[i].bid && j < result.seatbid[i].bid.length; j++) { - registerBid(result.seatbid[i].bid[j]); - } - } - } catch (ex) {} + if (responses.seatbid) { + let temp = []; + for (i = 0; i < responses.seatbid.length; i++) { + for (let j = 0; j < responses.seatbid[i].bid.length; j++) { + temp.push(responses.seatbid[i].bid[j]); + } } + responses = temp; + } - // if no bids are successful, inform prebid - noBids(params); - }); + for (i = 0; i < responses.length; i++) { + let bid = responses[i]; + let bidRequest = slotsToBids[bid.impid]; + let bidResponse = { + requestId: bidRequest.bidId, + bidderCode: that.code, + cpm: parseFloat(bid.price), + width: bid.w, + height: bid.h, + creativeId: bid.crid, + currency: 'USD', + netRevenue: true, + ttl: 1000 + }; + + if (bidRequest.mediaTypes && bidRequest.mediaTypes.video) { + bidResponse.vastUrl = bid.nurl; + bidResponse.mediaType = 'video'; + bidResponse.ttl = 10000; + } else { + bidResponse.ad = bid.adm; + } + bids.push(bidResponse); + } - logToConsole('version: ' + version); + return bids; }; } -adaptermanager.registerBidAdapter(new RhythmoneAdapter(), 'rhythmone', { - supportedMediaTypes: ['video'] -}); - -module.exports = RhythmoneAdapter; +export const spec = new RhythmOneBidAdapter(); +registerBidder(spec); diff --git a/modules/rhythmoneBidAdapter.md b/modules/rhythmoneBidAdapter.md new file mode 100644 index 00000000000..d08baaecea8 --- /dev/null +++ b/modules/rhythmoneBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: RhythmOne Bidder Adapter +Module Type: Bidder Adapter +Maintainer: astocker@rhythmone.com +``` + +# Description + +This module relays Prebid bids from Rhythm Exchange, RhythmOne's ad exchange. + +# Test Parameters + +```js +const adUnits = [{ + code: 'uuddlrlrbass', + sizes: [ + [300, 250] + ], + bids: [ + { + bidder: 'rhythmone', + params: + { + placementId: '411806', + endpoint: "//tag.1rx.io/rmp/72721/0/mvo?z=1r" // only required for testing. this api guarantees no 204 responses + } + } + ] +}]; +``` \ No newline at end of file diff --git a/modules/rockyouBidAdapter.js b/modules/rockyouBidAdapter.js new file mode 100644 index 00000000000..0748d6842a6 --- /dev/null +++ b/modules/rockyouBidAdapter.js @@ -0,0 +1,370 @@ +import * as utils from 'src/utils'; +import { Renderer } from 'src/Renderer'; +import { BANNER, VIDEO } from 'src/mediaTypes'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'rockyou'; + +const BASE_REQUEST_PATH = 'https://tas.rockyou.net/servlet/rotator/'; +const IFRAME_SYNC_URL = 'https://prebid.tas-sync.rockyou.net/usersync2/prebid'; +const VAST_PLAYER_LOCATION = 'https://rya-static.rockyou.com/rya/js/PreBidPlayer.js'; +export const ROTATION_ZONE = 'prod'; + +let isBidRequestValid = (bid) => { + return !!bid.params && !!bid.params.placementId; +}; + +/** +* The RockYou Ad Serving system currently only accepts one placementId +* per Ad request. For this reason, the first placementId indicated +* will be chosen as the predominant placementId for this request. +*/ +let determineOptimalPlacementId = (bidRequest) => { + return bidRequest.params.placementId; +} + +let determineOptimalRequestId = (bidRequest) => { + return bidRequest.bidId; +} + +let buildSiteComponent = (bidRequest) => { + let topLocation = utils.getTopWindowLocation(); + + let site = { + domain: topLocation.hostname, + page: topLocation.href, + ref: topLocation.origin + }; + + return site; +} + +let buildDeviceComponent = (bidRequest) => { + let device = { + js: 1, + language: ('language' in navigator) ? navigator.language : null + }; + + return device; +}; + +let extractValidSize = (bidRequest) => { + let width = null; + let height = null; + + let requestedSizes = []; + let mediaTypes = bidRequest.mediaTypes; + if (mediaTypes && ((mediaTypes.banner && mediaTypes.banner.sizes) || (mediaTypes.video && mediaTypes.video.playerSize))) { + if (mediaTypes.banner) { + requestedSizes = mediaTypes.banner.sizes; + } else { + requestedSizes = [mediaTypes.video.playerSize]; + } + } else if (!utils.isEmpty(bidRequest.sizes)) { + requestedSizes = bidRequest.sizes + } + + // Ensure the size array is normalized + let conformingSize = utils.parseSizesInput(requestedSizes); + + if (!utils.isEmpty(conformingSize) && conformingSize[0] != null) { + // Currently only the first size is utilized + let splitSizes = conformingSize[0].split('x'); + + width = parseInt(splitSizes[0]); + height = parseInt(splitSizes[1]); + } + + return { + w: width, + h: height + }; +}; + +let generateVideoComponent = (bidRequest) => { + let impSize = extractValidSize(bidRequest); + + return { + w: impSize.w, + h: impSize.h + } +} + +let generateBannerComponent = (bidRequest) => { + let impSize = extractValidSize(bidRequest); + + return { + w: impSize.w, + h: impSize.h + } +} + +let generateImpBody = (bidRequest) => { + let mediaTypes = bidRequest.mediaTypes; + + let banner = null; + let video = null; + + // Assume banner if the mediatype is not included + if (mediaTypes && mediaTypes.video) { + video = generateVideoComponent(bidRequest); + } else { + banner = generateBannerComponent(bidRequest); + } + + return { + id: bidRequest.index, + banner: banner, + video: video + }; +} + +let generatePayload = (bidRequest) => { + // Generate the expected OpenRTB payload + + let payload = { + id: determineOptimalRequestId(bidRequest), + site: buildSiteComponent(bidRequest), + device: buildDeviceComponent(bidRequest), + imp: [generateImpBody(bidRequest)] + }; + + return JSON.stringify(payload); +}; + +let overridableProperties = (request) => { + let rendererLocation = VAST_PLAYER_LOCATION; + let baseRequestPath = BASE_REQUEST_PATH; + let rotationZone = ROTATION_ZONE; + + if (!utils.isEmpty(request.rendererOverride)) { + rendererLocation = request.rendererOverride; + } + + if (request.params) { + if (!utils.isEmpty(request.params.baseRequestPath)) { + baseRequestPath = request.params.baseRequestPath; + } + + if (!utils.isEmpty(request.params.rotationZone)) { + rotationZone = request.params.rotationZone; + } + } + + return { + rendererLocation, + baseRequestPath, + rotationZone + } +} + +let buildRequests = (validBidRequests, requestRoot) => { + const requestType = 'POST'; + + let requestUrl = null; + let requestPayload = null; + let mediaTypes = null; + let adUnitCode = null; + let rendererOverride = null; + + let results = []; + // Due to the nature of how URLs are generated, there must + // be at least one bid request present for this to function + // correctly + if (!utils.isEmpty(validBidRequests)) { + results = validBidRequests.map( + bidRequest => { + let serverLocations = overridableProperties(bidRequest); + + // requestUrl is the full endpoint w/ relevant adspot paramters + let placementId = determineOptimalPlacementId(bidRequest); + requestUrl = `${serverLocations.baseRequestPath}${placementId}/0/vo?z=${serverLocations.rotationZone}`; + + // requestPayload is the POST body JSON for the OpenRtb request + requestPayload = generatePayload(bidRequest); + + mediaTypes = bidRequest.mediaTypes; + adUnitCode = bidRequest.adUnitCode; + rendererOverride = bidRequest.rendererOverride; + + return { + method: requestType, + type: requestType, + url: requestUrl, + data: requestPayload, + mediaTypes, + requestId: requestRoot.bidderRequestId, + bidId: bidRequest.bidId, + adUnitCode, + rendererOverride + }; + } + ); + } + + return results; +}; + +let outstreamRender = (bid) => { + // push to render queue because player may not be loaded yet + bid.renderer.push(() => { + let adUnitCode = bid.renderer.config.adUnitCode; + + try { + RockYouVastPlayer.render(adUnitCode, bid, playerCallbacks(bid.renderer)); + } catch (pbe) { + utils.logError(pbe); + } + }); +} + +let rockYouEventTranslation = (rockYouEvent) => { + let translated; + switch (rockYouEvent) { + case 'LOAD': + translated = 'loaded'; + break; + case 'IMPRESSION': + translated = 'impression'; + break; + case 'COMPLETE': + case 'ERROR': + translated = 'ended' + break; + } + + return translated; +} + +let playerCallbacks = (renderer) => (id, eventName) => { + eventName = rockYouEventTranslation(eventName); + + if (eventName) { + renderer.handleVideoEvent({ id, eventName }); + } +}; + +let generateRenderer = (bid, adUnitCode, rendererLocation) => { + let renderer = Renderer.install({ + url: rendererLocation, + config: { + adText: `RockYou Outstream Video Ad`, + adUnitCode: adUnitCode + }, + id: bid.id + }); + + bid.renderer = renderer; + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + renderer.setEventHandlers({ + impression: () => utils.logMessage('RockYou outstream video impression event'), + loaded: () => utils.logMessage('RockYou outstream video loaded event'), + ended: () => { + utils.logMessage('RockYou outstream renderer video event'); + document.querySelector(`#${adUnitCode}`).style.display = 'none'; + } + }); + + return renderer; +}; + +let interpretResponse = (serverResponse, request) => { + let responses = []; + + if (serverResponse.body) { + let responseBody = serverResponse.body; + + if (responseBody != null) { + let seatBids = responseBody.seatbid; + + if (!(utils.isEmpty(seatBids) || + utils.isEmpty(seatBids[0].bid))) { + let bid = seatBids[0].bid[0]; + + // handle any values that may end up undefined + let nullify = (value) => typeof value === 'undefined' ? null : value; + + let ttl = null; + if (bid.ext) { + ttl = nullify(bid.ext.ttl); + } + + let bidWidth = nullify(bid.w); + let bidHeight = nullify(bid.h); + + let bannerCreative = null; + let videoXml = null; + let mediaType = null; + let renderer = null; + + if (request.mediaTypes && request.mediaTypes.video) { + videoXml = bid.adm; + mediaType = VIDEO; + + let serversideLocations = overridableProperties(request); + + renderer = generateRenderer(bid, request.adUnitCode, serversideLocations.rendererLocation); + } else { + bannerCreative = bid.adm; + } + + let response = { + requestId: request.bidId, + cpm: bid.price, + width: bidWidth, + height: bidHeight, + ad: bannerCreative, + ttl: ttl, + creativeId: bid.adid, + netRevenue: true, + currency: responseBody.cur, + vastUrl: null, + vastXml: videoXml, + dealId: null, + mediaType: mediaType, + renderer: renderer + }; + + responses.push(response); + } + } + } + + return responses; +}; + +let getUserSyncs = (syncOptions, serverResponses) => { + const syncs = [] + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: IFRAME_SYNC_URL + }); + } + + return syncs; +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['ry'], + isBidRequestValid: isBidRequestValid, + buildRequests: buildRequests, + interpretResponse: interpretResponse, + getUserSyncs: getUserSyncs, + supportedMediaTypes: [BANNER, VIDEO] +}; + +export const internals = { + playerCallbacks, + generateRenderer +} + +registerBidder(spec); diff --git a/modules/rockyouBidAdapter.md b/modules/rockyouBidAdapter.md new file mode 100644 index 00000000000..1c6d2708b99 --- /dev/null +++ b/modules/rockyouBidAdapter.md @@ -0,0 +1,58 @@ +# Overview + +``` +Module Name: RockYou Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid.adapter@rockyou.com +``` + +# Description + +Connects to the RockYou exchange for bids. + +The RockYou bid adapter supports Banner and Video. + +For publishers who wish to be set up on the RockYou Ad Network, please contact +publishers@rockyou.com. + +RockYou user syncing requires the `userSync.iframeEnabled` property be set to `true`. + +# Test PARAMETERS +``` +var adUnits = [ + + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[720, 480]] + } + }, + + bids: [{ + bidder: 'rockyou', + params: { + placementId: '4954' + } + }] + }, + + // Video (outstream) + { + code: 'video-outstream', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [720, 480] + } + }, + bids: [{ + bidder: 'rockyou', + params: { + placementId: '4957' + } + }] + } +] +``` diff --git a/modules/roxotAnalyticsAdapter.js b/modules/roxotAnalyticsAdapter.js index 0a274660699..65771ad245d 100644 --- a/modules/roxotAnalyticsAdapter.js +++ b/modules/roxotAnalyticsAdapter.js @@ -1,6 +1,7 @@ import adapter from 'src/AnalyticsAdapter'; import CONSTANTS from 'src/constants.json'; import adaptermanager from 'src/adaptermanager'; +import includes from 'core-js/library/fn/array/includes'; const utils = require('src/utils'); @@ -106,7 +107,7 @@ function checkAdUnitConfig() { function buildBidWon(eventType, args) { bidWon.options = initOptions; if (checkAdUnitConfig()) { - if (initOptions.adUnits.includes(args.adUnitCode)) { + if (includes(initOptions.adUnits, args.adUnitCode)) { bidWon.events = [{ args: args, eventType: eventType }]; } } else { @@ -121,7 +122,7 @@ function buildEventStack() { function filterBidsByAdUnit(bids) { var filteredBids = []; bids.forEach(function (bid) { - if (initOptions.adUnits.includes(bid.placementCode)) { + if (includes(initOptions.adUnits, bid.placementCode)) { filteredBids.push(bid); } }); @@ -131,7 +132,7 @@ function filterBidsByAdUnit(bids) { function isValidEvent(eventType, adUnitCode) { if (checkAdUnitConfig()) { let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; - if (!initOptions.adUnits.includes(adUnitCode) && validationEvents.includes(eventType)) { + if (!includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType)) { return false; } } diff --git a/modules/roxotBidAdapter.js b/modules/roxotBidAdapter.js deleted file mode 100644 index a2b9b6ca6dc..00000000000 --- a/modules/roxotBidAdapter.js +++ /dev/null @@ -1,116 +0,0 @@ -var CONSTANTS = require('src/constants.json'); -var utils = require('src/utils.js'); -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader'); -var adaptermanager = require('src/adaptermanager'); - -var RoxotAdapter = function RoxotAdapter() { - var roxotUrl = 'r.rxthdr.com'; - - $$PREBID_GLOBAL$$.roxotResponseHandler = roxotResponseHandler; - - return { - callBids: _callBids - }; - - function _callBids(bidReqs) { - utils.logInfo('callBids roxot adapter invoking'); - - var domain = window.location.host; - var page = window.location.pathname + location.search + location.hash; - - var roxotBidReqs = { - id: utils.getUniqueIdentifierStr(), - bids: bidReqs, - site: { - domain: domain, - page: page - } - }; - - var scriptUrl = '//' + roxotUrl + '?callback=$$PREBID_GLOBAL$$.roxotResponseHandler' + - '&src=' + CONSTANTS.REPO_AND_VERSION + - '&br=' + encodeURIComponent(JSON.stringify(roxotBidReqs)); - - adloader.loadScript(scriptUrl); - } - - function roxotResponseHandler(roxotResponseObject) { - utils.logInfo('roxotResponseHandler invoking'); - var placements = []; - - if (isResponseInvalid()) { - return fillPlacementEmptyBid(); - } - - roxotResponseObject.bids.forEach(pushRoxotBid); - var allBidResponse = fillPlacementEmptyBid(placements); - utils.logInfo('roxotResponse handler finish'); - - return allBidResponse; - - function isResponseInvalid() { - return !roxotResponseObject || !roxotResponseObject.bids || !Array.isArray(roxotResponseObject.bids) || roxotResponseObject.bids.length <= 0; - } - - function pushRoxotBid(roxotBid) { - var placementCode = ''; - - var bidReq = $$PREBID_GLOBAL$$ - ._bidsRequested.find(bidSet => bidSet.bidderCode === 'roxot') - .bids.find(bid => bid.bidId === roxotBid.bidId); - - if (!bidReq) { - return pushErrorBid(placementCode); - } - - bidReq.status = CONSTANTS.STATUS.GOOD; - - placementCode = bidReq.placementCode; - placements.push(placementCode); - - var cpm = roxotBid.cpm; - var responseNurl = ''; - - if (!cpm) { - return pushErrorBid(placementCode); - } - - var bid = bidfactory.createBid(1, bidReq); - - bid.creative_id = roxotBid.id; - bid.bidderCode = 'roxot'; - bid.cpm = cpm; - bid.ad = decodeURIComponent(roxotBid.adm + responseNurl); - bid.width = parseInt(roxotBid.w); - bid.height = parseInt(roxotBid.h); - - bidmanager.addBidResponse(placementCode, bid); - } - - function fillPlacementEmptyBid(places) { - $$PREBID_GLOBAL$$ - ._bidsRequested.find(bidSet => bidSet.bidderCode === 'roxot') - .bids.forEach(fillIfNotFilled); - - function fillIfNotFilled(bid) { - if (utils.contains(places, bid.placementCode)) { - return null; - } - - pushErrorBid(bid); - } - } - - function pushErrorBid(bidRequest) { - var bid = bidfactory.createBid(2, bidRequest); - bid.bidderCode = 'roxot'; - bidmanager.addBidResponse(bidRequest.placementCode, bid); - } - } -}; - -adaptermanager.registerBidAdapter(new RoxotAdapter(), 'roxot'); - -module.exports = RoxotAdapter; diff --git a/modules/rtbdemandAdkBidAdapter.js b/modules/rtbdemandAdkBidAdapter.js new file mode 100644 index 00000000000..a7ec8463c17 --- /dev/null +++ b/modules/rtbdemandAdkBidAdapter.js @@ -0,0 +1,187 @@ +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'; + +const VIDEO_TARGETING = ['mimes', 'minduration', 'maxduration', 'protocols', + 'startdelay', 'linearity', 'boxingallowed', 'playbackmethod', 'delivery', + 'pos', 'api', 'ext']; +const VERSION = '1.1'; + +/** + * Adapter for requesting bids from RtbdemandAdk white-label display platform + */ +export const spec = { + + code: 'rtbdemandadk', + aliases: ['headbidding'], + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: function(bidRequest) { + return 'params' in bidRequest && typeof bidRequest.params.host !== 'undefined' && + 'zoneId' in bidRequest.params && !isNaN(Number(bidRequest.params.zoneId)); + }, + buildRequests: function(bidRequests) { + let auctionId; + let dispatch = bidRequests.map(buildImp) + .reduce((acc, curr, index) => { + let bidRequest = bidRequests[index]; + let zoneId = bidRequest.params.zoneId; + let host = bidRequest.params.host; + acc[host] = acc[host] || {}; + acc[host][zoneId] = acc[host][zoneId] || []; + acc[host][zoneId].push(curr); + auctionId = bidRequest.bidderRequestId; + return acc; + }, {}); + let requests = []; + Object.keys(dispatch).forEach(host => { + Object.keys(dispatch[host]).forEach(zoneId => { + const request = buildRtbRequest(dispatch[host][zoneId], auctionId); + requests.push({ + method: 'GET', + url: `${window.location.protocol}//${host}/rtbg`, + data: { + zone: Number(zoneId), + ad_type: 'rtb', + v: VERSION, + r: JSON.stringify(request) + } + }); + }); + }); + return requests; + }, + interpretResponse: function(serverResponse, request) { + let response = serverResponse.body; + if (!response.seatbid) { + return []; + } + + let rtbRequest = JSON.parse(request.data.r); + let rtbImps = rtbRequest.imp; + 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 prBid = { + requestId: rtbBid.impid, + cpm: rtbBid.price, + creativeId: rtbBid.crid, + currency: 'USD', + ttl: 360, + netRevenue: true + }; + if ('banner' in imp) { + prBid.mediaType = BANNER; + prBid.width = rtbBid.w; + prBid.height = rtbBid.h; + prBid.ad = formatAdMarkup(rtbBid); + } + if ('video' in imp) { + prBid.mediaType = VIDEO; + prBid.vastUrl = rtbBid.nurl; + prBid.width = imp.video.w; + prBid.height = imp.video.h; + } + return prBid; + }); + }, + getUserSyncs: function(syncOptions, serverResponses) { + if (!syncOptions.iframeEnabled || !serverResponses || serverResponses.length === 0) { + return []; + } + return serverResponses.filter(rsp => rsp.body && rsp.body.ext && rsp.body.ext.adk_usersync) + .map(rsp => rsp.body.ext.adk_usersync) + .reduce((a, b) => a.concat(b), []) + .map(sync_url => ({type: 'iframe', url: sync_url})); + } +}; + +registerBidder(spec); + +/** + * Builds parameters object for single impression + */ +function buildImp(bid) { + const sizes = bid.sizes; + const imp = { + 'id': bid.bidId, + 'tagid': bid.placementCode + }; + + if (bid.mediaType === 'video') { + imp.video = {w: sizes[0], h: sizes[1]}; + if (bid.params.video) { + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => imp.video[param] = bid.params.video[param]); + } + } else { + imp.banner = { + format: sizes.map(s => ({'w': s[0], 'h': s[1]})), + topframe: 0 + }; + } + if (utils.getTopWindowLocation().protocol === 'https:') { + imp.secure = 1; + } + return imp; +} + +/** + * Builds complete rtb request + * @param imps collection of impressions + * @param auctionId + */ +function buildRtbRequest(imps, auctionId) { + let req = { + 'id': auctionId, + 'imp': imps, + 'site': createSite(), + 'at': 1, + 'device': { + 'ip': 'caller', + 'ua': 'caller', + 'js': 1, + 'language': getLanguage() + }, + 'ext': { + 'adk_usersync': 1 + } + }; + if (utils.getDNT()) { + req.device.dnt = 1; + } + return req; +} + +function getLanguage() { + const language = navigator.language ? 'language' : 'userLanguage'; + return navigator[language].split('-')[0]; +} + +/** + * Creates site description object + */ +function createSite() { + var location = utils.getTopWindowLocation(); + return { + 'domain': location.hostname, + 'page': location.href.split('?')[0] + }; +} + +/** + * Format creative with optional nurl call + * @param bid rtb Bid object + */ +function formatAdMarkup(bid) { + var adm = bid.adm; + if ('nurl' in bid) { + adm += utils.createTrackPixelHtml(`${bid.nurl}&px=1`); + } + return `${adm}`; +} diff --git a/modules/rtbdemandAdkBidAdapter.md b/modules/rtbdemandAdkBidAdapter.md new file mode 100644 index 00000000000..d75df08f167 --- /dev/null +++ b/modules/rtbdemandAdkBidAdapter.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: RtbdemandAdk Bidder Adapter +Module Type: Bidder Adapter +Maintainer: shreyanschopra@rtbdemand.com +``` + +# Description + +Connects to RtbdemandAdk whitelabel platform. +Banner and video formats are supported. + + +# Test Parameters +``` + var adUnits = [ + { + code: 'banner-ad-div', + sizes: [[300, 250]], // banner size + bids: [ + { + bidder: 'rtbdemandadk', + params: { + zoneId: '30164', //required parameter + host: 'cpm.metaadserving.com' //required parameter + } + } + ] + }, { + code: 'video-ad-player', + sizes: [640, 480], // video player size + bids: [ + { + bidder: 'rtbdemandadk', + mediaType : 'video', + params: { + zoneId: '30164', //required parameter + host: 'cpm.metaadserving.com' //required parameter + } + } + ] + } + ]; +``` diff --git a/modules/rtbdemandBidAdapter.js b/modules/rtbdemandBidAdapter.js new file mode 100644 index 00000000000..66440e4291b --- /dev/null +++ b/modules/rtbdemandBidAdapter.js @@ -0,0 +1,123 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'rtbdemand'; +const BIDDER_SERVER = 'bidding.rtbdemand.com'; +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.zoneid); + }, + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + var server = bidRequest.params.server || BIDDER_SERVER; + var parse = getSize(bidderRequest.bids[0].sizes); + const payload = { + from: 'hb', + v: '1.0', + request_id: bidRequest.bidderRequestId, + imp_id: bidRequest.bidId, + aff: bidRequest.params.zoneid, + bid_floor: parseFloat(bidRequest.params.floor) > 0 ? bidRequest.params.floor : 0, + charset: document.charSet || document.characterSet, + site_domain: document.location.hostname, + site_page: window.location.href, + subid: 'hb', + flashver: getFlashVersion(), + tmax: bidderRequest.timeout, + hb: '1', + name: document.location.hostname, + width: parse.width, + height: parse.height, + device_width: screen.width, + device_height: screen.height, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + secure: isSecure(), + make: navigator.vendor ? navigator.vendor : '', + }; + if (document.referrer) { + payload.referrer = document.referrer; + } + + return { + method: 'GET', + url: '//' + server + '/hb', + data: payload + }; + }); + }, + interpretResponse: function(serverResponse) { + serverResponse = serverResponse.body; + const bidResponses = []; + if (serverResponse && serverResponse.seatbid) { + serverResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => { + const bidResponse = { + requestId: bid.impid, + creativeId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + netRevenue: true, + currency: 'USD', + ttl: 360, + }; + + bidResponses.push(bidResponse); + })); + } + return bidResponses; + }, + getUserSyncs: function getUserSyncs(syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: '//' + BIDDER_SERVER + '/delivery/matches.php?type=iframe', + }]; + } + } +} + +function getFlashVersion() { + var plugins, plugin, result; + + if (navigator.plugins && navigator.plugins.length > 0) { + plugins = navigator.plugins; + for (var i = 0; i < plugins.length && !result; i++) { + plugin = plugins[i]; + if (plugin.name.indexOf('Shockwave Flash') > -1) { + result = plugin.description.split('Shockwave Flash ')[1]; + } + } + } + return result || ''; +} + +/* Get parsed size from request size */ +function getSize(requestSizes) { + const parsed = {}; + const size = utils.parseSizesInput(requestSizes)[0]; + + if (typeof size !== 'string') { + return parsed; + } + + const parsedSize = size.toUpperCase().split('X'); + const width = parseInt(parsedSize[0], 10); + if (width) { + parsed.width = width; + } + + const height = parseInt(parsedSize[1], 10); + if (height) { + parsed.height = height; + } + + return parsed; +} + +function isSecure() { + return document.location.protocol === 'https:'; +} + +registerBidder(spec); diff --git a/modules/rtbdemandBidAdapter.md b/modules/rtbdemandBidAdapter.md new file mode 100644 index 00000000000..2727d85e084 --- /dev/null +++ b/modules/rtbdemandBidAdapter.md @@ -0,0 +1,26 @@ +# Overview + +**Module Name**: Rtbdemand Media fmxSSP Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: rtb@rtbdemand.com + +# Description + +Connects to Rtbdemand Media fmxSSP demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [{ + code: 'banner-ad-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'rtbdemand', + params: { + zoneid: '9999', + floor: 0.005, + server: 'bidding.rtbdemand.com' + } + }] + }]; + +``` diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js new file mode 100644 index 00000000000..cd60925b2c7 --- /dev/null +++ b/modules/rtbhouseBidAdapter.js @@ -0,0 +1,118 @@ +import * as utils from 'src/utils'; +import { BANNER } from 'src/mediaTypes'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import includes from 'core-js/library/fn/array/includes'; + +const BIDDER_CODE = 'rtbhouse'; +const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia']; +const ENDPOINT_URL = 'creativecdn.com/bidder/prebid/bids'; +const DEFAULT_CURRENCY_ARR = ['USD']; // NOTE - USD is the only supported currency right now; Hardcoded for bids + +/** + * Helpers + */ + +function buildEndpointUrl(region) { + return 'https://' + region + '.' + ENDPOINT_URL; +} + +/** + * Produces an OpenRTBImpression from a slot config. + */ +function mapImpression(slot) { + return { + id: slot.bidId, + banner: mapBanner(slot), + tagid: slot.adUnitCode.toString(), + }; +} + +/** + * Produces an OpenRTB Banner object for the slot given. + */ +function mapBanner(slot) { + return { + w: slot.sizes[0][0], + h: slot.sizes[0][1], + format: mapSizes(slot.sizes) + }; +} + +/** + * Produce openRTB banner.format object + */ +function mapSizes(slot_sizes) { + const format = []; + slot_sizes.forEach(elem => { + format.push({ + w: elem[0], + h: elem[1] + }); + }); + return format; +} + +/** + * Produces an OpenRTB site object. + */ +function mapSite(validRequest) { + const pubId = validRequest && validRequest.length > 0 ? validRequest[0].params.publisherId : 'unknown'; + return { + publisher: { + id: pubId.toString(), + }, + page: utils.getTopWindowUrl(), + name: utils.getOrigin() + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!(includes(REGIONS, bid.params.region) && bid.params.publisherId); + }, + + buildRequests: function (validBidRequests) { + const request = { + id: validBidRequests[0].auctionId, + imp: validBidRequests.map(slot => mapImpression(slot)), + site: mapSite(validBidRequests), + cur: DEFAULT_CURRENCY_ARR, + test: validBidRequests[0].params.test || 0 + }; + return { + method: 'POST', + url: buildEndpointUrl(validBidRequests[0].params.region), + data: JSON.stringify(request) + }; + }, + interpretResponse: function (serverResponse, originalRequest) { + serverResponse = serverResponse.body; + const bids = []; + + if (utils.isArray(serverResponse)) { + serverResponse.forEach(serverBid => { + if (serverBid.price !== 0) { + const bid = { + requestId: serverBid.impid, + mediaType: BANNER, + cpm: serverBid.price, + creativeId: serverBid.adid, + ad: serverBid.adm, + width: serverBid.w, + height: serverBid.h, + ttl: 55, + netRevenue: true, + currency: 'USD' + }; + bids.push(bid); + } + }); + } + return bids; + } +}; + +registerBidder(spec); diff --git a/modules/rtbhouseBidAdapter.md b/modules/rtbhouseBidAdapter.md new file mode 100644 index 00000000000..47deed1a277 --- /dev/null +++ b/modules/rtbhouseBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +Module Name: RTB House Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@rtbhouse.com + +# Description + +Connects to RTB House unique demand. +Banner formats are supported. +Unique publisherId is required. +Please reach out to pmp@rtbhouse.com to receive your own + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "rtbhouse", + params: { + region: 'prebid-eu', + publisherId: 'PREBID_TEST_ID' + } + } + ] + } + ]; +``` diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js new file mode 100644 index 00000000000..dd111efe03c --- /dev/null +++ b/modules/rubiconAnalyticsAdapter.js @@ -0,0 +1,452 @@ +import adapter from 'src/AnalyticsAdapter'; +import adaptermanager from 'src/adaptermanager'; +import CONSTANTS from 'src/constants.json'; +import { ajax } from 'src/ajax'; +import { config } from 'src/config'; +import * as utils from 'src/utils'; + +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BIDDER_DONE, + BID_TIMEOUT, + BID_WON, + SET_TARGETING + }, + STATUS: { + GOOD, + NO_BID + } +} = CONSTANTS; + +let serverConfig; +config.getConfig('s2sConfig', ({s2sConfig}) => { + serverConfig = s2sConfig; +}); + +export const SEND_TIMEOUT = 3000; +const INTEGRATION = 'pbjs'; + +const cache = { + auctions: {}, + targeting: {}, + timeouts: {}, +}; + +// basically lodash#pick that also allows transformation functions and property renaming +function _pick(obj, properties) { + 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; + }, {}); +} + +function stringProperties(obj) { + return Object.keys(obj).reduce((newObj, prop) => { + let value = obj[prop]; + if (typeof value === 'number') { + value = value.toFixed(3); + } else if (typeof value !== 'string') { + value = String(value); + } + newObj[prop] = value; + return newObj; + }, {}); +} + +function sizeToDimensions(size) { + return { + width: size.w || size[0], + height: size.h || size[1] + }; +} + +function validMediaType(type) { + return ['banner', 'native', 'video'].indexOf(type) !== -1; +} + +function formatSource(src) { + if (typeof src === 'undefined') { + src = 'client'; + } else if (src === 's2s') { + src = 'server'; + } + return src.toLowerCase(); +} + +function sendMessage(auctionId, bidWonId) { + function formatBid(bid) { + return _pick(bid, [ + 'bidder', + 'bidId', + 'status', + 'error', + 'source', (source, bid) => { + if (source) { + return source; + } + return serverConfig && Array.isArray(serverConfig.bidders) && serverConfig.bidders.indexOf(bid.bidder) !== -1 + ? 'server' : 'client' + }, + 'clientLatencyMillis', + 'serverLatencyMillis', + 'params', + 'bidResponse', bidResponse => bidResponse ? _pick(bidResponse, [ + 'bidPriceUSD', + 'dealId', + 'dimensions', + 'mediaType' + ]) : undefined + ]); + } + function formatBidWon(bid) { + return Object.assign(formatBid(bid), _pick(bid.adUnit, [ + 'adUnitCode', + 'transactionId', + 'videoAdFormat', () => bid.videoAdFormat, + 'mediaTypes' + ]), { + adserverTargeting: stringProperties(cache.targeting[bid.adUnit.adUnitCode] || {}), + bidwonStatus: 'success', // hard-coded for now + accountId, + samplingFactor + }); + } + let referrer = config.getConfig('pageUrl') || utils.getTopWindowUrl(); + let message = { + eventTimeMillis: Date.now(), + integration: INTEGRATION, + version: '$prebid.version$', + referrerUri: referrer + }; + let auctionCache = cache.auctions[auctionId]; + if (auctionCache && !auctionCache.sent) { + let adUnitMap = Object.keys(auctionCache.bids).reduce((adUnits, bidId) => { + let bid = auctionCache.bids[bidId]; + let adUnit = adUnits[bid.adUnit.adUnitCode]; + if (!adUnit) { + adUnit = adUnits[bid.adUnit.adUnitCode] = _pick(bid.adUnit, [ + 'adUnitCode', + 'transactionId', + 'mediaTypes', + 'dimensions', + 'adserverTargeting', () => stringProperties(cache.targeting[bid.adUnit.adUnitCode] || {}) + ]); + adUnit.bids = []; + } + + if (bid.videoAdFormat && !adUnit.videoAdFormat) { + adUnit.videoAdFormat = bid.videoAdFormat; + } + + // determine adUnit.status from its bid statuses. Use priority below to determine, higher index is better + let statusPriority = ['error', 'no-bid', 'success']; + if (statusPriority.indexOf(bid.status) > statusPriority.indexOf(adUnit.status)) { + adUnit.status = bid.status; + } + + adUnit.bids.push(formatBid(bid)); + + return adUnits; + }, {}); + + let auction = { + clientTimeoutMillis: auctionCache.timeout, + samplingFactor, + accountId, + adUnits: Object.keys(adUnitMap).map(i => adUnitMap[i]) + }; + + if (serverConfig) { + auction.serverTimeoutMillis = serverConfig.timeout; + } + + message.auctions = [auction]; + + let bidsWon = Object.keys(auctionCache.bidsWon).reduce((memo, adUnitCode) => { + let bidId = auctionCache.bidsWon[adUnitCode]; + if (bidId) { + memo.push(formatBidWon(auctionCache.bids[bidId])); + } + return memo; + }, []); + + if (bidsWon.length > 0) { + message.bidsWon = bidsWon; + } + + auctionCache.sent = true; + } else if (bidWonId && auctionCache && auctionCache.bids[bidWonId]) { + message.bidsWon = [ + formatBidWon(auctionCache.bids[bidWonId]) + ]; + } + + ajax( + this.getUrl(), + null, + JSON.stringify(message), + { + contentType: 'application/json' + } + ); +} + +function parseBidResponse(bid) { + return _pick(bid, [ + 'getCpmInNewCurrency as bidPriceUSD', (fn) => { + if (bid.currency === 'USD') { + return Number(bid.cpm); + } + // use currency conversion function if present + if (typeof fn === 'function') { + return Number(fn('USD')); + } + // TODO: throw error or something if not USD and currency module wasn't present? + }, + 'dealId', + 'status', + 'mediaType', + 'dimensions', () => _pick(bid, [ + 'width', + 'height' + ]) + ]); +} + +let samplingFactor = 1; +let accountId; + +let baseAdapter = adapter({analyticsType: 'endpoint'}); +let rubiconAdapter = Object.assign({}, baseAdapter, { + enableAnalytics(config = {}) { + let error = false; + samplingFactor = 1; + + if (typeof config.options === 'object') { + if (config.options.accountId) { + accountId = Number(config.options.accountId); + } + if (config.options.endpoint) { + this.getUrl = () => config.options.endpoint; + } else { + utils.logError('required endpoint missing from rubicon analytics'); + error = true; + } + if (typeof config.options.sampling !== 'undefined') { + samplingFactor = 1 / parseFloat(config.options.sampling); + } + if (typeof config.options.samplingFactor !== 'undefined') { + if (typeof config.options.sampling !== 'undefined') { + utils.logWarn('Both options.samplingFactor and options.sampling enabled in rubicon analytics, defaulting to samplingFactor'); + } + samplingFactor = parseFloat(config.options.samplingFactor); + config.options.sampling = 1 / samplingFactor; + } + } + + let validSamplingFactors = [1, 10, 20, 40, 100]; + if (validSamplingFactors.indexOf(samplingFactor) === -1) { + error = true; + utils.logError('invalid samplingFactor for rubicon analytics: ' + samplingFactor + ', must be one of ' + validSamplingFactors.join(', ')); + } else if (!accountId) { + error = true; + utils.logError('required accountId missing for rubicon analytics'); + } + + if (!error) { + baseAdapter.enableAnalytics.call(this, config); + } + }, + disableAnalytics() { + this.getUrl = baseAdapter.getUrl; + accountId = null; + baseAdapter.disableAnalytics.apply(this, arguments); + }, + track({eventType, args}) { + switch (eventType) { + case AUCTION_INIT: + let cacheEntry = _pick(args, [ + 'timestamp', + 'timeout' + ]); + cacheEntry.bids = {}; + cacheEntry.bidsWon = {}; + cache.auctions[args.auctionId] = cacheEntry; + break; + case BID_REQUESTED: + Object.assign(cache.auctions[args.auctionId].bids, args.bids.reduce((memo, bid) => { + // mark adUnits we expect bidWon events for + cache.auctions[args.auctionId].bidsWon[bid.adUnitCode] = false; + + memo[bid.bidId] = _pick(bid, [ + 'bidder', bidder => bidder.toLowerCase(), + 'bidId', + 'status', () => 'no-bid', // default a bid to no-bid until response is recieved or bid is timed out + 'finalSource as source', + 'params', (params, bid) => { + switch (bid.bidder) { + // specify bidder params we want here + case 'rubicon': + return _pick(params, [ + 'accountId', + 'siteId', + 'zoneId' + ]); + } + }, + 'videoAdFormat', (_, cachedBid) => { + if (cachedBid.bidder === 'rubicon') { + return ({ + 201: 'pre-roll', + 202: 'interstitial', + 203: 'outstream', + 204: 'mid-roll', + 205: 'post-roll', + 207: 'vertical' + })[utils.deepAccess(bid, 'params.video.size_id')]; + } else { + let startdelay = parseInt(utils.deepAccess(bid, 'params.video.startdelay'), 10); + if (!isNaN(startdelay)) { + if (startdelay > 0) { + return 'mid-roll'; + } + return ({ + '0': 'pre-roll', + '-1': 'mid-roll', + '-2': 'post-roll' + })[startdelay] + } + } + }, + 'adUnit', () => _pick(bid, [ + 'adUnitCode', + 'transactionId', + 'sizes as dimensions', sizes => sizes.map(sizeToDimensions), + 'mediaTypes', (types) => { + if (bid.mediaType && validMediaType(bid.mediaType)) { + return [bid.mediaType]; + } + if (Array.isArray(types)) { + return types.filter(validMediaType); + } + if (typeof types === 'object') { + if (!bid.sizes) { + bid.dimensions = []; + utils._each(types, (type) => + bid.dimensions = bid.dimensions.concat( + type.sizes.map(sizeToDimensions) + ) + ); + } + return Object.keys(types).filter(validMediaType); + } + return ['banner']; + } + ]) + ]); + return memo; + }, {})); + break; + case BID_RESPONSE: + let bid = cache.auctions[args.auctionId].bids[args.adId]; + bid.source = formatSource(bid.source || args.source); + switch (args.getStatusCode()) { + case GOOD: + bid.status = 'success'; + break; + case NO_BID: + bid.status = 'no-bid'; + break; + default: + bid.status = 'error'; + bid.error = { + code: 'request-error' + }; + } + bid.clientLatencyMillis = Date.now() - cache.auctions[args.auctionId].timestamp; + if (typeof args.serverResponseTimeMs !== 'undefined') { + bid.serverLatencyMillis = args.serverResponseTimeMs; + } + bid.bidResponse = parseBidResponse(args); + break; + case BIDDER_DONE: + args.bids.forEach(bid => { + let cachedBid = cache.auctions[bid.auctionId].bids[bid.bidId]; + if (!cachedBid.status) { + cachedBid.status = 'no-bid'; + } + if (!cachedBid.clientLatencyMillis) { + cachedBid.clientLatencyMillis = Date.now() - cache.auctions[bid.auctionId].timestamp; + } + }); + break; + case SET_TARGETING: + Object.assign(cache.targeting, args); + break; + case BID_WON: + let auctionCache = cache.auctions[args.auctionId]; + auctionCache.bidsWon[args.adUnitCode] = args.adId; + + // check if this BID_WON missed the boat, if so send by itself + if (auctionCache.sent === true) { + sendMessage.call(this, args.auctionId, args.adId); + } else if (Object.keys(auctionCache.bidsWon).reduce((memo, adUnitCode) => { + // only send if we've received bidWon events for all adUnits in auction + memo = memo && auctionCache.bidsWon[adUnitCode]; + return memo; + }, true)) { + clearTimeout(cache.timeouts[args.auctionId]); + delete cache.timeouts[args.auctionId]; + + sendMessage.call(this, args.auctionId); + } + break; + case AUCTION_END: + // start timer to send batched payload just in case we don't hear any BID_WON events + cache.timeouts[args.auctionId] = setTimeout(() => { + sendMessage.call(this, args.auctionId); + }, SEND_TIMEOUT); + break; + case BID_TIMEOUT: + args.forEach(badBid => { + let auctionCache = cache.auctions[badBid.auctionId]; + let bid = auctionCache.bids[badBid.bidId]; + bid.status = 'error'; + bid.error = { + code: 'timeout-error' + }; + }); + break; + } + } +}); + +adaptermanager.registerAnalyticsAdapter({ + adapter: rubiconAdapter, + code: 'rubicon' +}); + +export default rubiconAdapter; diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 69981ba2b56..d6a69fda625 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,10 +1,9 @@ import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import {config} from 'src/config'; +import {BANNER, VIDEO} from 'src/mediaTypes'; -// use deferred function call since version isn't defined yet at this point -function getIntegration() { - return 'pbjs_lite_' + $$PREBID_GLOBAL$$.version; -} +const INTEGRATION = 'pbjs_lite_v$prebid.version$'; function isSecure() { return location.protocol === 'https:'; @@ -13,13 +12,14 @@ function isSecure() { // use protocol relative urls for http or https const FASTLANE_ENDPOINT = '//fastlane.rubiconproject.com/a/api/fastlane.json'; const VIDEO_ENDPOINT = '//fastlane-adv.rubiconproject.com/v1/auction/video'; -const SYNC_ENDPOINT = 'https://tap-secure.rubiconproject.com/partner/scripts/rubicon/emily.html?rtb_ext=1'; +const SYNC_ENDPOINT = 'https://eus.rubiconproject.com/usync.html'; const TIMEOUT_BUFFER = 500; var sizeMap = { 1: '468x60', 2: '728x90', + 5: '120x90', 8: '120x600', 9: '160x600', 10: '300x600', @@ -58,23 +58,28 @@ var sizeMap = { 101: '480x320', 102: '768x1024', 103: '480x280', + 108: '320x240', 113: '1000x300', 117: '320x100', 125: '800x250', 126: '200x600', - 195: '600x300' + 144: '980x600', + 195: '600x300', + 199: '640x200', + 213: '1030x590', + 214: '980x360', }; utils._each(sizeMap, (item, key) => sizeMap[item] = key); export const spec = { code: 'rubicon', aliases: ['rubiconLite'], - supportedMediaTypes: ['video'], + supportedMediaTypes: [BANNER, VIDEO], /** * @param {object} bid * @return boolean */ - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { if (typeof bid.params !== 'object') { return false; } @@ -84,16 +89,31 @@ export const spec = { return false; } + // Log warning if context is 'outstream', is not currently supported + if (utils.deepAccess(bid, `mediaTypes.${VIDEO}.context`) === 'outstream') { + utils.logWarn('Warning: outstream video for Rubicon Client Adapter is not supported yet'); + } + + // Log warning if mediaTypes contains both 'banner' and 'video' + if (spec.hasVideoMediaType(bid) && typeof utils.deepAccess(bid, `mediaTypes.${BANNER}`) !== 'undefined') { + utils.logWarn('Warning: instream video and banner requested for same ad unit, continuing with video instream request'); + } + + // Bid is invalid if legacy video is set but params video is missing size_id + if (bid.mediaType === 'video' && typeof utils.deepAccess(bid, 'params.video.size_id') === 'undefined') { + return false; + } + + // Bid is invalid if mediaTypes video is invalid and a mediaTypes banner property is not defined + if (bid.mediaTypes && !spec.hasVideoMediaType(bid) && typeof bid.mediaTypes.banner === 'undefined') { + return false; + } + let parsedSizes = parseSizes(bid); if (parsedSizes.length < 1) { return false; } - if (bid.mediaType === 'video') { - if (typeof params.video !== 'object' || !params.video.size_id) { - return false; - } - } return true; }, /** @@ -101,22 +121,34 @@ export const spec = { * @param bidderRequest * @return ServerRequest[] */ - buildRequests: function(bidRequests, bidderRequest) { + buildRequests: function (bidRequests, bidderRequest) { return bidRequests.map(bidRequest => { bidRequest.startTime = new Date().getTime(); - if (bidRequest.mediaType === 'video') { + let page_url = config.getConfig('pageUrl'); + if (bidRequest.params.referrer) { + page_url = bidRequest.params.referrer; + } else if (!page_url) { + page_url = utils.getTopWindowUrl(); + } + + // GDPR reference, for use by 'banner' and 'video' + const gdprConsent = bidderRequest.gdprConsent; + + if (spec.hasVideoMediaType(bidRequest)) { let params = bidRequest.params; let size = parseSizes(bidRequest); let data = { - page_url: !params.referrer ? utils.getTopWindowUrl() : params.referrer, + page_url, resolution: _getScreenResolution(), account_id: params.accountId, - integration: getIntegration(), + integration: INTEGRATION, + 'x_source.tid': bidRequest.transactionId, timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart + TIMEOUT_BUFFER), stash_creatives: true, ae_pass_through_parameters: params.video.aeParams, + rp_secure: bidRequest.params.secure !== false, slots: [] }; @@ -124,10 +156,10 @@ export const spec = { let slotData = { site_id: params.siteId, zone_id: params.zoneId, - position: params.position || 'btf', + position: parsePosition(params.position), floor: parseFloat(params.floor) > 0.01 ? params.floor : 0.01, - element_id: bidRequest.placementCode, - name: bidRequest.placementCode, + element_id: bidRequest.adUnitCode, + name: bidRequest.adUnitCode, language: params.video.language, width: size[0], height: size[1], @@ -148,6 +180,14 @@ export const spec = { data.slots.push(slotData); + if (gdprConsent) { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + data.gdpr = Number(gdprConsent.gdprApplies); + } + data.gdpr_consent = gdprConsent.consentString; + } + return { method: 'POST', url: VIDEO_ENDPOINT, @@ -167,7 +207,7 @@ export const spec = { visitor, inventory, userId, - referrer: pageUrl + latLong: [latitude, longitude] = [], } = bidRequest.params; // defaults @@ -187,13 +227,23 @@ export const spec = { 'p_pos', position, 'rp_floor', floor, 'rp_secure', isSecure() ? '1' : '0', - 'tk_flint', getIntegration(), - 'tid', bidRequest.transactionId, + 'tk_flint', INTEGRATION, + 'x_source.tid', bidRequest.transactionId, 'p_screen_res', _getScreenResolution(), 'kw', keywords, - 'tk_user_key', userId + 'tk_user_key', userId, + 'p_geo.latitude', parseFloat(latitude).toFixed(4), + 'p_geo.longitude', parseFloat(longitude).toFixed(4) ]; + if (gdprConsent) { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + data.push('gdpr', Number(gdprConsent.gdprApplies)); + } + data.push('gdpr_consent', gdprConsent.consentString); + } + if (visitor !== null && typeof visitor === 'object') { utils._each(visitor, (item, key) => data.push(`tg_v.${key}`, item)); } @@ -204,14 +254,14 @@ export const spec = { data.push( 'rand', Math.random(), - 'rf', !pageUrl ? utils.getTopWindowUrl() : pageUrl + 'rf', page_url ); data = data.concat(_getDigiTrustQueryParams()); data = data.reduce( (memo, curr, index) => - index % 2 === 0 && data[index + 1] !== undefined + index % 2 === 0 && data[index + 1] !== undefined && !isNaN(data[index + 1]) ? memo + curr + '=' + encodeURIComponent(data[index + 1]) + '&' : memo, '' ).slice(0, -1); // remove trailing & @@ -224,12 +274,23 @@ export const spec = { }; }); }, + /** + * Test if bid has mediaType or mediaTypes set for video. + * note: 'mediaType' has been deprecated, however support will remain for a transitional period + * @param {BidRequest} bidRequest + * @returns {boolean} + */ + hasVideoMediaType: function (bidRequest) { + return (typeof utils.deepAccess(bidRequest, 'params.video.size_id') !== 'undefined' && + (bidRequest.mediaType === VIDEO || utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}.context`) === 'instream')); + }, /** * @param {*} responseObj * @param {BidRequest} bidRequest * @return {Bid[]} An array of bids which */ - interpretResponse: function(responseObj, {bidRequest}) { + interpretResponse: function (responseObj, {bidRequest}) { + responseObj = responseObj.body; let ads = responseObj.ads; // check overall response @@ -238,8 +299,8 @@ export const spec = { } // video ads array is wrapped in an object - if (typeof bidRequest === 'object' && bidRequest.mediaType === 'video' && typeof ads === 'object') { - ads = ads[bidRequest.placementCode]; + if (typeof bidRequest === 'object' && spec.hasVideoMediaType(bidRequest) && typeof ads === 'object') { + ads = ads[bidRequest.adUnitCode]; } // check the ad response @@ -258,17 +319,27 @@ export const spec = { let bid = { requestId: bidRequest.bidId, currency: 'USD', - creative_id: ad.creative_id, - bidderCode: spec.code, + creativeId: ad.creative_id, cpm: ad.cpm || 0, - dealId: ad.deal + dealId: ad.deal, + ttl: 300, // 5 minutes + netRevenue: config.getConfig('rubicon.netRevenue') || false, + rubicon: { + advertiserId: ad.advertiser, + networkId: ad.network + } }; - if (bidRequest.mediaType === 'video') { + + if (ad.creative_type) { + bid.mediaType = ad.creative_type; + } + + if (ad.creative_type === VIDEO) { bid.width = bidRequest.params.video.playerWidth; bid.height = bidRequest.params.video.playerHeight; bid.vastUrl = ad.creative_depot_url; - bid.descriptionUrl = ad.impression_id; bid.impression_id = ad.impression_id; + bid.videoCacheKey = ad.impression_id; } else { bid.ad = _renderCreative(ad.script, ad.impression_id); [bid.width, bid.height] = sizeMap[ad.size_id].split('x').map(num => Number(num)); @@ -279,15 +350,15 @@ export const spec = { .reduce((memo, item) => { memo[item.key] = item.values[0]; return memo; - }, {'rpfl_elemid': bidRequest.placementCode}); + }, {'rpfl_elemid': bidRequest.adUnitCode}); bids.push(bid); return bids; }, []); }, - getUserSyncs: function() { - if (!hasSynced) { + getUserSyncs: function (syncOptions) { + if (!hasSynced && syncOptions.iframeEnabled) { hasSynced = true; return { type: 'iframe', @@ -307,9 +378,10 @@ function _getScreenResolution() { function _getDigiTrustQueryParams() { function getDigiTrustId() { - let digiTrustUser = window.DigiTrust && ($$PREBID_GLOBAL$$.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'})); + let digiTrustUser = window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'})); return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; } + let digiTrustId = getDigiTrustId(); // Verify there is an ID and this user has not opted out if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { @@ -336,61 +408,76 @@ function _renderCreative(script, impId) { function parseSizes(bid) { let params = bid.params; - if (bid.mediaType === 'video') { + if (spec.hasVideoMediaType(bid)) { let size = []; - if (params.video.playerWidth && params.video.playerHeight) { + if (params.video && params.video.playerWidth && params.video.playerHeight) { size = [ params.video.playerWidth, params.video.playerHeight ]; - } else if ( - Array.isArray(bid.sizes) && bid.sizes.length > 0 && - Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1 - ) { + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0 && Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1) { size = bid.sizes[0]; } return size; } - return masSizeOrdering(Array.isArray(params.sizes) - ? params.sizes.map(size => (sizeMap[size] || '').split('x')) : bid.sizes - ); -} -export function masSizeOrdering(sizes) { - const MAS_SIZE_PRIORITY = [15, 2, 9]; + // deprecated: temp legacy support + let sizes = Array.isArray(params.sizes) ? params.sizes : mapSizes(bid.sizes) + return masSizeOrdering(sizes); +} + +function mapSizes(sizes) { return utils.parseSizesInput(sizes) - // map sizes while excluding non-matches + // map sizes while excluding non-matches .reduce((result, size) => { let mappedSize = parseInt(sizeMap[size], 10); if (mappedSize) { result.push(mappedSize); } return result; - }, []) - .sort((first, second) => { - // sort by MAS_SIZE_PRIORITY priority order - const firstPriority = MAS_SIZE_PRIORITY.indexOf(first); - const secondPriority = MAS_SIZE_PRIORITY.indexOf(second); - - if (firstPriority > -1 || secondPriority > -1) { - if (firstPriority === -1) { - return 1; - } - if (secondPriority === -1) { - return -1; - } - return firstPriority - secondPriority; + }, []); +} + +function parsePosition(position) { + if (position === 'atf' || position === 'btf') { + return position; + } + return 'unknown'; +} + +export function masSizeOrdering(sizes) { + const MAS_SIZE_PRIORITY = [15, 2, 9]; + + return sizes.sort((first, second) => { + // sort by MAS_SIZE_PRIORITY priority order + const firstPriority = MAS_SIZE_PRIORITY.indexOf(first); + const secondPriority = MAS_SIZE_PRIORITY.indexOf(second); + + if (firstPriority > -1 || secondPriority > -1) { + if (firstPriority === -1) { + return 1; } + if (secondPriority === -1) { + return -1; + } + return firstPriority - secondPriority; + } - // and finally ascending order - return first - second; - }); + // and finally ascending order + return first - second; + }); } var hasSynced = false; + export function resetUserSync() { hasSynced = false; } +function isNaN(value) { + // eslint-disable-next-line no-self-compare + return value !== value; +} + registerBidder(spec); diff --git a/modules/rubiconBidAdapter.md b/modules/rubiconBidAdapter.md new file mode 100644 index 00000000000..b1871882a9a --- /dev/null +++ b/modules/rubiconBidAdapter.md @@ -0,0 +1,48 @@ +# Overview + +``` +Module Name: Rubicon Project Bid Adapter +Module Type: Bidder Adapter +Maintainer: header-bidding@rubiconproject.com +``` + +# Description + +Connect to Rubicon Project's exchange for bids. + +The Rubicon Project adapter requires setup and approval from the +Rubicon Project team. Please reach out to your account team or +globalsupport@rubiconproject.com for more information. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "rubicon", + params: { + accountId: 1001, + siteId: 113932, + zoneId: 535510 + } + } + ] + },{ + code: 'test-div', + sizes: [[300, 50]], + bids: [ + { + bidder: "rubicon", + params: { + accountId: 1001, + siteId: 113932, + zoneId: 535510 + } + } + ] + } + ]; +``` diff --git a/modules/rxrtbBidAdapter.js b/modules/rxrtbBidAdapter.js new file mode 100644 index 00000000000..37aa20b68cd --- /dev/null +++ b/modules/rxrtbBidAdapter.js @@ -0,0 +1,140 @@ +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 = 'rxrtb'; +const DEFAULT_HOST = 'bid.rxrtb.bid'; +const AUCTION_TYPE = 2; +const RESPONSE_TTL = 900; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bidRequest) { + return 'params' in bidRequest && bidRequest.params.source !== undefined && bidRequest.params.id !== undefined && utils.isInteger(bidRequest.params.id) && bidRequest.params.token !== undefined; + }, + buildRequests: function (validBidRequests) { + var requests = []; + for (let i = 0; i < validBidRequests.length; i++) { + let prebidReq = makePrebidRequest(validBidRequests[i]); + if (prebidReq) { + requests.push(prebidReq); + } + } + + return requests; + }, + interpretResponse: function (serverResponse, bidRequest) { + let rtbResp = serverResponse.body; + if ((!rtbResp) || (!rtbResp.seatbid)) { + return []; + } + let bidResponses = []; + for (let i = 0; i < rtbResp.seatbid.length; i++) { + let seatbid = rtbResp.seatbid[i]; + for (let j = 0; j < seatbid.bid.length; j++) { + let bid = seatbid.bid[j]; + let bidResponse = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + mediaType: BANNER, + creativeId: bid.crid, + currency: rtbResp.cur || 'USD', + netRevenue: true, + ttl: bid.exp || RESPONSE_TTL, + ad: bid.adm + }; + bidResponses.push(bidResponse); + } + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + return []; + } +} + +registerBidder(spec); + +function getDomain(url) { + var a = document.createElement('a'); + a.href = url; + + return a.host; +} + +function makePrebidRequest(req) { + let host = req.params.host || DEFAULT_HOST; + let url = '//' + host + '/dsp?id=' + req.params.id + '&token=' + req.params.token; + let reqData = makeRtbRequest(req); + return { + method: 'POST', + url: url, + data: JSON.stringify(reqData) + }; +} + +function makeRtbRequest(req) { + let imp = []; + imp.push(makeImp(req)); + return { + 'id': req.auctionId, + 'imp': imp, + 'site': makeSite(req), + 'device': makeDevice(), + 'hb': 1, + 'at': req.params.at || AUCTION_TYPE, + 'cur': ['USD'], + 'badv': req.params.badv || '', + 'bcat': req.params.bcat || '', + }; +} + +function makeImp(req) { + let imp = { + 'id': req.bidId, + 'tagid': req.adUnitCode, + 'banner': makeBanner(req) + }; + + if (req.params.bidfloor && isFinite(req.params.bidfloor)) { + imp.bidfloor = req.params.bidfloor + } + + return imp; +} + +function makeBanner(req) { + let format = []; + let banner = {}; + for (let i = 0; i < req.sizes.length; i++) { + format.push({ + w: req.sizes[i][0], + h: req.sizes[i][1] + }); + } + banner.format = format; + if (req.params.pos && utils.isInteger(req.params.pos)) { + banner.pos = req.params.pos; + } + return banner; +} + +function makeSite(req) { + return { + 'id': req.params.source, + 'domain': getDomain(config.getConfig('publisherDomain')), + 'page': utils.getTopWindowUrl(), + 'ref': utils.getTopWindowReferrer() + }; +} + +function makeDevice() { + return { + 'ua': window.navigator.userAgent || '', + 'ip': 1 + }; +} diff --git a/modules/rxrtbBidAdapter.md b/modules/rxrtbBidAdapter.md new file mode 100644 index 00000000000..e9628bed0dc --- /dev/null +++ b/modules/rxrtbBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: rxrtb Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: contact@picellaltd.com + + +# Description + +Module that connects to rxrtb's demand source + +# Test Parameters +```javascript + var adUnits = [ + { + code: 'test-ad', + sizes: [[728, 98]], + bids: [ + { + bidder: 'rxrtb', + params: { + id: 89, + token: '658f11a5efbbce2f9be3f1f146fcbc22', + source: 'prebidtest' + } + } + ] + }, + ]; +``` \ No newline at end of file diff --git a/modules/s2sTesting.js b/modules/s2sTesting.js index a821383dc2d..60ab150530f 100644 --- a/modules/s2sTesting.js +++ b/modules/s2sTesting.js @@ -1,8 +1,6 @@ import { config } from 'src/config'; import { setS2STestingModule } from 'src/adaptermanager'; -var CONSTANTS = require('src/constants.json'); -const AST = CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING; export const SERVER = 'server'; export const CLIENT = 'client'; @@ -12,43 +10,9 @@ var bidSource = {}; // store bidder sources determined from s2sConfing bidderCon // load s2sConfig config.getConfig('s2sConfig', config => { testing = config.s2sConfig && config.s2sConfig.testing; - addBidderSourceTargeting(config.s2sConfig) calculateBidSources(config.s2sConfig); }); -// function to add hb_source_ adServerTargeting (AST) kvp to bidder settings -function addBidderSourceTargeting(s2sConfig = {}) { - // bail if testing is not turned on - if (!testing) { - return; - } - var bidderSettings = $$PREBID_GLOBAL$$.bidderSettings || {}; - var bidderControl = s2sConfig.bidderControl || {}; - // for each configured bidder - (s2sConfig.bidders || []).forEach((bidder) => { - // remove any existing kvp setting - if (bidderSettings[bidder] && bidderSettings[bidder][AST]) { - bidderSettings[bidder][AST] = bidderSettings[bidder][AST].filter((kvp) => { - return kvp.key !== `hb_source_${bidder}`; - }); - } - // if includeSourceKvp === true add new kvp setting - if (bidderControl[bidder] && bidderControl[bidder].includeSourceKvp) { - bidderSettings[bidder] = bidderSettings[bidder] || {}; - bidderSettings[bidder][AST] = bidderSettings[bidder][AST] || []; - bidderSettings[bidder][AST].push({ - key: `hb_source_${bidder}`, - val: function (bidResponse) { - // default to client (currently only S2S sets this) - return bidResponse.source || CLIENT; - } - }); - // make sure "alwaysUseBid" is true so targeting is set - bidderSettings[bidder].alwaysUseBid = true; - } - }); -} - export function getSourceBidderMap(adUnits = []) { var sourceBidders = {[SERVER]: {}, [CLIENT]: {}}; diff --git a/modules/saraBidAdapter.js b/modules/saraBidAdapter.js new file mode 100644 index 00000000000..233fba65cc7 --- /dev/null +++ b/modules/saraBidAdapter.js @@ -0,0 +1,141 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'sara'; +const ENDPOINT_URL = '//ad.sara.media/hb'; +const ADAPTER_SYNC_URL = '//ad.sara.media/push_sync'; +const TIME_TO_LIVE = 360; +const LOG_ERROR_MESS = { + noAuid: 'Bid from response has no auid parameter - ', + noAdm: 'Bid from response has no adm parameter - ', + noBid: 'Array of bid objects is empty', + noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', + emptyUids: 'Uids should be not empty', + emptySeatbid: 'Seatbid array from response has empty item', + emptyResponse: 'Response is empty', + hasEmptySeatbidArray: 'Response has empty seatbid array', + hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' +}; + +/** + * Dentsu Aegis Network Marketplace Bid Adapter. + * Contact: niels@baarsma.net + * + */ +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function(bid) { + return !!bid.params.uid; + }, + + buildRequests: function(validBidRequests) { + const auids = []; + const bidsMap = {}; + const bids = validBidRequests || []; + let priceType = 'net'; + let reqId; + + bids.forEach(bid => { + if (bid.params.priceType === 'gross') { + priceType = 'gross'; + } + if (!bidsMap[bid.params.uid]) { + bidsMap[bid.params.uid] = [bid]; + auids.push(bid.params.uid); + } else { + bidsMap[bid.params.uid].push(bid); + } + reqId = bid.bidderRequestId; + }); + + const payload = { + u: utils.getTopWindowUrl(), + pt: priceType, + auids: auids.join(','), + r: reqId, + }; + + return { + method: 'GET', + url: ENDPOINT_URL, + data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bidsMap: bidsMap, + }; + }, + + interpretResponse: function(serverResponse, bidRequest) { + serverResponse = serverResponse && serverResponse.body + const bidResponses = []; + const bidsMap = bidRequest.bidsMap; + const priceType = bidRequest.data.pt; + + let errorMessage; + + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; + } + + if (!errorMessage && serverResponse.seatbid) { + serverResponse.seatbid.forEach(respItem => { + _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses); + }); + } + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: ADAPTER_SYNC_URL + }]; + } + } +} + +function _getBidFromResponse(respItem) { + if (!respItem) { + utils.logError(LOG_ERROR_MESS.emptySeatbid); + } else if (!respItem.bid) { + utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + } else if (!respItem.bid[0]) { + utils.logError(LOG_ERROR_MESS.noBid); + } + return respItem && respItem.bid && respItem.bid[0]; +} + +function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { + if (!serverBid) return; + let errorMessage; + if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); + if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + awaitingBids.forEach(bid => { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: priceType !== 'gross', + ttl: TIME_TO_LIVE, + ad: serverBid.adm, + dealId: serverBid.dealid + }; + bidResponses.push(bidResponse); + }); + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } + if (errorMessage) { + utils.logError(errorMessage); + } +} + +registerBidder(spec); diff --git a/modules/saraBidAdapter.md b/modules/saraBidAdapter.md new file mode 100755 index 00000000000..65572528181 --- /dev/null +++ b/modules/saraBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: Sara Bidder Adapter +Module Type: Bidder Adapter +Maintainer: github@sara.media + +# Description + +Module that connects to Sara demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "sara", + params: { + uid: '5', + priceType: 'gross' // by default is 'net' + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "sara", + params: { + uid: 6, + priceType: 'gross' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/sekindoUMBidAdapter.js b/modules/sekindoUMBidAdapter.js index ee36dd3c88a..cf8ba9e23f0 100644 --- a/modules/sekindoUMBidAdapter.js +++ b/modules/sekindoUMBidAdapter.js @@ -1,91 +1,115 @@ -import { getBidRequest } from 'src/utils.js'; -import { config } from 'src/config'; - -var CONSTANTS = require('src/constants.json'); -var utils = require('src/utils.js'); -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader.js'); -var adaptermanager = require('src/adaptermanager'); - -function SekindoUMAdapter() { - function _callBids(params) { - var bids = params.bids; - var bidsCount = bids.length; - - var pubUrl = null; - if (parent !== window) { pubUrl = document.referrer; } else { pubUrl = window.location.href; } - - for (var i = 0; i < bidsCount; i++) { - var bidReqeust = bids[i]; - var callbackId = bidReqeust.bidId; - _requestBids(bidReqeust, callbackId, pubUrl); - // store a reference to the bidRequest from the callback id - // bidmanager.pbCallbackMap[callbackId] = bidReqeust; - } - } - - $$PREBID_GLOBAL$$.sekindoCB = function(callbackId, response) { - var bidObj = getBidRequest(callbackId); - if (typeof (response) !== 'undefined' && typeof (response.cpm) !== 'undefined') { - var bid = []; - if (bidObj) { - var bidCode = bidObj.bidder; - var placementCode = bidObj.placementCode; - - if (response.cpm !== undefined && response.cpm > 0) { - bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD); - bid.callback_uid = callbackId; - bid.bidderCode = bidCode; - bid.creative_id = response.adId; - bid.cpm = parseFloat(response.cpm); - bid.ad = response.ad; - bid.width = response.width; - bid.height = response.height; - - bidmanager.addBidResponse(placementCode, bid); - } else { - bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); - bid.callback_uid = callbackId; - bid.bidderCode = bidCode; - bidmanager.addBidResponse(placementCode, bid); - } - } - } else { - if (bidObj) { - utils.logMessage('No prebid response for placement ' + bidObj.placementCode); - } else { - utils.logMessage('sekindoUM callback general error'); - } - } - }; - - function _requestBids(bid, callbackId, pubUrl) { - // determine tag params - var spaceId = utils.getBidIdParameter('spaceId', bid.params); - var subId = utils.getBidIdParameter('subId', bid.params); - var bidfloor = utils.getBidIdParameter('bidfloor', bid.params); - var protocol = (document.location.protocol === 'https:' ? 's' : ''); - var scriptSrc = 'http' + protocol + '://hb.sekindo.com/live/liveView.php?'; - - scriptSrc = utils.tryAppendQueryString(scriptSrc, 's', spaceId); - scriptSrc = utils.tryAppendQueryString(scriptSrc, 'subId', subId); - scriptSrc = utils.tryAppendQueryString(scriptSrc, 'pubUrl', pubUrl); - scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbcb', callbackId); - scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbver', '3'); - scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbobj', '$$PREBID_GLOBAL$$'); - scriptSrc = utils.tryAppendQueryString(scriptSrc, 'dcpmflr', bidfloor); - scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbto', config.getConfig('bidderTimeout')); - scriptSrc = utils.tryAppendQueryString(scriptSrc, 'protocol', protocol); - - adloader.loadScript(scriptSrc); - } - - return { - callBids: _callBids - }; -} - -adaptermanager.registerBidAdapter(new SekindoUMAdapter(), 'sekindoUM'); - -module.exports = SekindoUMAdapter; +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +export const spec = { + code: 'sekindoUM', + 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) { + if (bid.mediaType == 'video' || (typeof bid.mediaTypes == 'object' && typeof bid.mediaTypes.video == 'object')) { + if (typeof bid.params.video != 'object' || typeof bid.params.video.playerWidth == 'undefined' || typeof bid.params.video.playerHeight == 'undefined') { + return false; + } + } + return !!(bid.params.spaceId); + }, + /** + * 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) { + var pubUrl = null; + try { + if (window.top == window) { + pubUrl = window.location.href; + } else { + try { + pubUrl = window.top.location.href; + } catch (e2) { + pubUrl = document.referrer; + } + } + } catch (e1) {} + + return validBidRequests.map(bidRequest => { + var subId = utils.getBidIdParameter('subId', bidRequest.params); + var spaceId = utils.getBidIdParameter('spaceId', bidRequest.params); + var bidfloor = utils.getBidIdParameter('bidfloor', bidRequest.params); + var protocol = (document.location.protocol === 'https:' ? 's' : ''); + var queryString = ''; + + queryString = utils.tryAppendQueryString(queryString, 's', spaceId); + queryString = utils.tryAppendQueryString(queryString, 'subId', subId); + queryString = utils.tryAppendQueryString(queryString, 'pubUrl', pubUrl); + queryString = utils.tryAppendQueryString(queryString, 'hbTId', bidRequest.transactionId); + queryString = utils.tryAppendQueryString(queryString, 'hbBidId', bidRequest.bidId); + queryString = utils.tryAppendQueryString(queryString, 'hbver', '4'); + queryString = utils.tryAppendQueryString(queryString, 'hbcb', '1');/// legasy + queryString = utils.tryAppendQueryString(queryString, 'dcpmflr', bidfloor); + queryString = utils.tryAppendQueryString(queryString, 'protocol', protocol); + queryString = utils.tryAppendQueryString(queryString, 'x', bidRequest.params.width); + queryString = utils.tryAppendQueryString(queryString, 'y', bidRequest.params.height); + if (bidRequest.mediaType === 'video' || (typeof bidRequest.mediaTypes == 'object' && typeof bidRequest.mediaTypes.video == 'object')) { + queryString = utils.tryAppendQueryString(queryString, 'x', bidRequest.params.playerWidth); + queryString = utils.tryAppendQueryString(queryString, 'y', bidRequest.params.playerHeight); + if (typeof vid_vastType != 'undefined') { + queryString = utils.tryAppendQueryString(queryString, 'vid_vastType', bidRequest.params.vid_vastType); + } + if (typeof bidRequest.mediaTypes == 'object' && typeof bidRequest.mediaTypes.video == 'object' && typeof bidRequest.mediaTypes.video.context == 'string') { + queryString = utils.tryAppendQueryString(queryString, 'vid_context', bidRequest.mediaTypes.video.context); + } + } + + var endpointUrl = 'http' + protocol + '://hb.sekindo.com/live/liveView.php'; + + return { + method: 'GET', + url: endpointUrl, + data: queryString, + }; + }); + }, + /** + * 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) { + if (typeof serverResponse !== 'object') { + return []; + } + + let bidResponses = []; + var bidResponse = { + requestId: serverResponse.body.id, + bidderCode: spec.code, + cpm: serverResponse.body.cpm, + width: serverResponse.body.width, + height: serverResponse.body.height, + creativeId: serverResponse.body.creativeId, + currency: serverResponse.body.currency, + netRevenue: serverResponse.body.netRevenue, + ttl: serverResponse.body.ttl + }; + if (bidRequest.mediaType == 'video') { + if (typeof serverResponse.body.vastUrl != 'undefined') { + bidResponse.vastUrl = serverResponse.body.vastUrl; + } else { + bidResponse.vastXml = serverResponse.body.vastXml; + } + } else { + bidResponse.ad = serverResponse.body.ad; + } + + bidResponses.push(bidResponse); + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/sekindoUMBidAdapter.md b/modules/sekindoUMBidAdapter.md new file mode 100755 index 00000000000..eeffff928eb --- /dev/null +++ b/modules/sekindoUMBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +**Module Name**: sekindoUM Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: nissime@sekindo.com + +# Description + +Connects to Sekindo (part of UM) demand source to fetch bids. +Banner, Outstream and Native formats are supported. + + +# Test Parameters +``` + var adUnits = [{ + code: 'banner-ad-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'sekindoUM', + params: { + spaceId: 14071 + width:300, ///optional + height:250, //optional + } + }] + }, + { + code: 'video-ad-div', + sizes: [[640, 480]], + bids: [{ + bidder: 'sekindoUM', + params: { + spaceId: 87812, + video:{ + playerWidth:640, + playerHeight:480, + vid_vastType: 5 //optional + } + } + }] + } + ]; +``` diff --git a/modules/serverbidBidAdapter.js b/modules/serverbidBidAdapter.js index f5044fe4ae1..8497a67f401 100644 --- a/modules/serverbidBidAdapter.js +++ b/modules/serverbidBidAdapter.js @@ -1,105 +1,77 @@ -import Adapter from 'src/adapter'; -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; -import { ajax } from 'src/ajax'; -import adaptermanager from 'src/adaptermanager'; - -var ServerBidAdapter; -ServerBidAdapter = function ServerBidAdapter() { - const baseAdapter = new Adapter('serverbid'); - - const CONFIG = { - 'serverbid': { - 'BASE_URI': 'https://e.serverbid.com/api/v2', - 'SMARTSYNC_BASE_URI': 'https://s.zkcdn.net/ss' - }, - 'connectad': { - 'BASE_URI': 'https://i.connectad.io/api/v2', - 'SMARTSYNC_BASE_URI': 'https://s.zkcdn.net/ss' - }, - 'onefiftytwo': { - 'BASE_URI': 'https://e.serverbid.com/api/v2', - 'SMARTSYNC_BASE_URI': 'https://s.zkcdn.net/ss' - } - }; - - const SMARTSYNC_CALLBACK = 'serverbidCallBids'; - - const sizeMap = [ - null, - '120x90', - '120x90', - '468x60', - '728x90', - '300x250', - '160x600', - '120x600', - '300x100', - '180x150', - '336x280', - '240x400', - '234x60', - '88x31', - '120x60', - '120x240', - '125x125', - '220x250', - '250x250', - '250x90', - '0x0', - '200x90', - '300x50', - '320x50', - '320x480', - '185x185', - '620x45', - '300x125', - '800x250' - ]; - - sizeMap[77] = '970x90'; - sizeMap[123] = '970x250'; - sizeMap[43] = '300x600'; - - const bidIds = []; - - baseAdapter.callBids = function(params) { - if (params && params.bids && - utils.isArray(params.bids) && - params.bids.length && - CONFIG[params.bidderCode]) { - const config = CONFIG[params.bidderCode]; - config.request = window[params.bidderCode.toUpperCase() + '_CONFIG']; - if (!window.SMARTSYNC) { - _callBids(config, params); - } else { - window[SMARTSYNC_CALLBACK] = function() { - window[SMARTSYNC_CALLBACK] = function() {}; - _callBids(config, params); - }; - - const siteId = params.bids[0].params.siteId; - _appendScript(config.SMARTSYNC_BASE_URI + '/' + siteId + '.js'); - - const sstimeout = window.SMARTSYNC_TIMEOUT || ((params.timeout || 500) / 2); - setTimeout(function() { - var cb = window[SMARTSYNC_CALLBACK]; - window[SMARTSYNC_CALLBACK] = function() {}; - cb(); - }, sstimeout); - } +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'serverbid'; + +const CONFIG = { + 'serverbid': { + 'BASE_URI': 'https://e.serverbid.com/api/v2' + }, + 'connectad': { + 'BASE_URI': 'https://i.connectad.io/api/v2' + }, + 'onefiftytwo': { + 'BASE_URI': 'https://e.serverbid.com/api/v2' + }, + 'insticator': { + 'BASE_URI': 'https://e.serverbid.com/api/v2' + }, + 'adsparc': { + 'BASE_URI': 'https://e.serverbid.com/api/v2' + }, + 'automatad': { + 'BASE_URI': 'https://e.serverbid.com/api/v2' + }, + 'archon': { + 'BASE_URI': 'https://e.serverbid.com/api/v2' + } +}; + +let siteId = 0; +let bidder = 'serverbid'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['connectad', 'onefiftytwo', 'insticator', 'adsparc', 'automatad', 'archon'], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params.networkId && bid.params.siteId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @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. + + let ret = { + method: 'POST', + url: '', + data: '', + bidRequest: [] + }; + + if (validBidRequests.length < 1) { + return ret; } - }; - function _appendScript(src) { - var script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = src; - document.getElementsByTagName('head')[0].appendChild(script); - } + let ENDPOINT_URL; + + // These variables are used in creating the user sync URL. + siteId = validBidRequests[0].params.siteId; + bidder = validBidRequests[0].params.bidder; - function _callBids(config, params) { const data = Object.assign({ placements: [], time: Date.now(), @@ -107,15 +79,13 @@ ServerBidAdapter = function ServerBidAdapter() { url: utils.getTopWindowUrl(), referrer: document.referrer, enableBotFiltering: true, - includePricingData: true - }, config.request); + includePricingData: true, + parallel: true + }, validBidRequests[0].params); - const bids = params.bids || []; - - for (let i = 0; i < bids.length; i++) { - const bid = bids[i]; - - bidIds.push(bid.bidId); + validBidRequests.map(bid => { + let config = CONFIG[bid.bidder]; + ENDPOINT_URL = config.BASE_URI; const placement = Object.assign({ divName: bid.bidId, @@ -125,84 +95,137 @@ ServerBidAdapter = function ServerBidAdapter() { if (placement.networkId && placement.siteId) { data.placements.push(placement); } - } + }); - if (data.placements.length) { - ajax(config.BASE_URI, _responseCallback, JSON.stringify(data), { method: 'POST', withCredentials: true, contentType: 'application/json' }); - } - } + ret.data = JSON.stringify(data); + ret.bidRequest = validBidRequests; + ret.url = ENDPOINT_URL; - function _responseCallback(result) { + return ret; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { let bid; + let bids; let bidId; let bidObj; - let bidCode; - let placementCode; + let bidResponses = []; - try { - result = JSON.parse(result); - } catch (error) { - utils.logError(error); - } + bids = bidRequest.bidRequest; - for (let i = 0; i < bidIds.length; i++) { - bidId = bidIds[i]; - bidObj = utils.getBidRequest(bidId); - bidCode = bidObj.bidder; - placementCode = bidObj.placementCode; + serverResponse = (serverResponse || {}).body; + for (let i = 0; i < bids.length; i++) { + bid = {}; + bidObj = bids[i]; + bidId = bidObj.bidId; - if (result) { - const decision = result.decisions && result.decisions[bidId]; + if (serverResponse) { + const decision = serverResponse.decisions && serverResponse.decisions[bidId]; const price = decision && decision.pricing && decision.pricing.clearPrice; if (decision && price) { - bid = bidfactory.createBid(1, bidObj); - bid.bidderCode = bidCode; + bid.requestId = bidId; bid.cpm = price; bid.width = decision.width; bid.height = decision.height; bid.ad = retrieveAd(decision); - } else { - bid = bidfactory.createBid(2, bidObj); - bid.bidderCode = bidCode; + bid.currency = 'USD'; + bid.creativeId = decision.adId; + bid.ttl = 360; + bid.netRevenue = true; + bid.referrer = utils.getTopWindowUrl(); + + bidResponses.push(bid); } - } else { - bid = bidfactory.createBid(2, bidObj); - bid.bidderCode = bidCode; } - bidmanager.addBidResponse(placementCode, bid); } - } - function retrieveAd(decision) { - return decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); - } + return bidResponses; + }, - function getSize(sizes) { - const result = []; - sizes.forEach(function(size) { - const index = sizeMap.indexOf(size[0] + 'x' + size[1]); - if (index >= 0) { - result.push(index); + getUserSyncs: function(syncOptions) { + if (syncOptions.iframeEnabled) { + if (bidder === 'connectad') { + return [{ + type: 'iframe', + url: '//cdn.connectad.io/connectmyusers.php' + }]; + } else { + return [{ + type: 'iframe', + url: '//s.zkcdn.net/ss/' + siteId + '.html' + }]; } - }); - return result; + } else { + utils.logWarn(bidder + ': Please enable iframe based user syncing.'); + } } - - // Export the `callBids` function, so that Prebid.js can execute - // this function when the page asks to send out bid requests. - return Object.assign(this, { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode - }); }; -ServerBidAdapter.createNew = function() { - return new ServerBidAdapter(); -}; +const sizeMap = [ + null, + '120x90', + '120x90', + '468x60', + '728x90', + '300x250', + '160x600', + '120x600', + '300x100', + '180x150', + '336x280', + '240x400', + '234x60', + '88x31', + '120x60', + '120x240', + '125x125', + '220x250', + '250x250', + '250x90', + '0x0', + '200x90', + '300x50', + '320x50', + '320x480', + '185x185', + '620x45', + '300x125', + '800x250' +]; + +sizeMap[77] = '970x90'; +sizeMap[123] = '970x250'; +sizeMap[43] = '300x600'; +sizeMap[286] = '970x66'; +sizeMap[3230] = '970x280'; +sizeMap[429] = '486x60'; +sizeMap[374] = '700x500'; +sizeMap[934] = '300x1050'; +sizeMap[1578] = '320x100'; +sizeMap[331] = '320x250'; +sizeMap[3301] = '320x267'; +sizeMap[2730] = '728x250'; + +function getSize(sizes) { + const result = []; + sizes.forEach(function(size) { + const index = sizeMap.indexOf(size[0] + 'x' + size[1]); + if (index >= 0) { + result.push(index); + } + }); + return result; +} -adaptermanager.registerBidAdapter(new ServerBidAdapter(), 'serverbid'); -adaptermanager.aliasBidAdapter('serverbid', 'connectad'); -adaptermanager.aliasBidAdapter('serverbid', 'onefiftytwo'); +function retrieveAd(decision) { + return decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); +} -module.exports = ServerBidAdapter; +registerBidder(spec); diff --git a/modules/serverbidBidAdapter.md b/modules/serverbidBidAdapter.md new file mode 100644 index 00000000000..87b51e665e2 --- /dev/null +++ b/modules/serverbidBidAdapter.md @@ -0,0 +1,44 @@ +# Overview + +Module Name: Serverbid Bid Adapter + +Module Type: Bid Adapter + +Maintainer: jgrimes@serverbid.com, jswart@serverbid.com + +# Description + +Connects to Serverbid for receiving bids from configured demand sources. + +# Test Parameters +```javascript + var adUnits = [ + { + code: 'test-ad-1', + sizes: [[300, 250]], + bids: [ + { + bidder: 'serverbid', + params: { + networkId: '9969', + siteId: '980639' + } + } + ] + }, + { + code: 'test-ad-2', + sizes: [[300, 250]], + bids: [ + { + bidder: 'serverbid', + params: { + networkId: '9969', + siteId: '980639', + zoneIds: [178503] + } + } + ] + } + ]; +``` diff --git a/modules/serverbidServerBidAdapter.js b/modules/serverbidServerBidAdapter.js new file mode 100644 index 00000000000..1025d29a6c0 --- /dev/null +++ b/modules/serverbidServerBidAdapter.js @@ -0,0 +1,233 @@ +import Adapter from 'src/adapter'; +import bidfactory from 'src/bidfactory'; +import * as utils from 'src/utils'; +import adaptermanager from 'src/adaptermanager'; +import { STATUS, S2S } from 'src/constants'; +import { config } from 'src/config'; + +const TYPE = S2S.SRC; +const getConfig = config.getConfig; +const REQUIRED_S2S_CONFIG_KEYS = ['siteId', 'networkId', 'bidders']; + +let _s2sConfig; + +const bidder = 'serverbidServer'; + +var ServerBidServerAdapter; +ServerBidServerAdapter = function ServerBidServerAdapter() { + const baseAdapter = new Adapter('serverbidServer'); + + const BASE_URI = 'https://e.serverbid.com/api/v2'; + + const sizeMap = [ + null, + '120x90', + '120x90', + '468x60', + '728x90', + '300x250', + '160x600', + '120x600', + '300x100', + '180x150', + '336x280', + '240x400', + '234x60', + '88x31', + '120x60', + '120x240', + '125x125', + '220x250', + '250x250', + '250x90', + '0x0', + '200x90', + '300x50', + '320x50', + '320x480', + '185x185', + '620x45', + '300x125', + '800x250' + ]; + + sizeMap[77] = '970x90'; + sizeMap[123] = '970x250'; + sizeMap[43] = '300x600'; + + function setS2sConfig(options) { + if (options.adapter != bidder) return; + + let contains = (xs, x) => xs.indexOf(x) > -1; + let userConfig = Object.keys(options); + + REQUIRED_S2S_CONFIG_KEYS.forEach(key => { + if (!contains(userConfig, key)) { + utils.logError(key + ' missing in server to server config'); + return void 0; // void 0 to beat the linter + } + }) + + _s2sConfig = options; + } + getConfig('s2sConfig', ({s2sConfig}) => setS2sConfig(s2sConfig)); + + function getLocalConfig() { + return (_s2sConfig || {}); + } + + function _convertFields(bid) { + let safeBid = bid || {}; + let converted = {}; + let name = safeBid.bidder; + converted[name] = safeBid.params; + return converted; + } + + baseAdapter.callBids = function(s2sBidRequest, bidRequests, addBidResponse, done, ajax) { + let params = s2sBidRequest; + let shouldDoWorkFn = function(bidRequest) { + return bidRequest && + bidRequest.ad_units && + utils.isArray(bidRequest.ad_units) && + bidRequest.ad_units.length; + } + if (shouldDoWorkFn(params)) { + _callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); + } + }; + + function _callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax) { + let bidRequest = s2sBidRequest; + + const data = { + placements: [], + time: Date.now(), + user: {}, + url: utils.getTopWindowUrl(), + referrer: document.referrer, + enableBotFiltering: true, + includePricingData: true, + parallel: true + }; + const allBids = []; + + for (let i = 0; i < bidRequest.ad_units.length; i++) { + let adunit = bidRequest.ad_units[i]; + let siteId = _s2sConfig.siteId; + let networkId = getLocalConfig().networkId; + let sizes = adunit.sizes; + + var bids = adunit.bids || []; + // one placement for each of the bids + for (let i = 0; i < bids.length; i++) { + const bid = bids[i]; + bid.code = adunit.code; + allBids.push(bid); + + const placement = Object.assign({}, { + divName: bid.bid_id, + networkId: networkId, + siteId: siteId, + adTypes: bid.adTypes || getSize(sizes), + bidders: _convertFields(bid), + skipSelection: true + }); + + if (placement.networkId && placement.siteId) { + data.placements.push(placement); + } + } + } + if (data.placements.length) { + ajax(BASE_URI, _responseCallback(addBidResponse, allBids, done), JSON.stringify(data), { method: 'POST', withCredentials: true, contentType: 'application/json' }); + } + } + + function _responseCallback(addBidResponse, bids, done) { + return function (resp) { + let bid; + let bidId; + let result; + let bidObj; + let bidCode; + let placementCode; + let skipSelectionRequestsReturnArray = function (decision) { + return (decision || []).length ? decision[0] : {}; + }; + + try { + result = JSON.parse(resp); + } catch (error) { + utils.logError(error); + } + + for (let i = 0; i < bids.length; i++) { + bidObj = bids[i]; + bidId = bidObj.bid_id; + bidObj.bidId = bidObj.bid_id; + bidCode = bidObj.bidder; + placementCode = bidObj.code; + let noBid = function(bidObj) { + bid = bidfactory.createBid(STATUS.NO_BID, bidObj); + bid.bidderCode = bidCode; + return bid; + }; + + if (result) { + const decision = result.decisions && skipSelectionRequestsReturnArray(result.decisions[bidId]); + const price = decision && decision.pricing && decision.pricing.clearPrice; + + if (decision && price) { + bid = bidfactory.createBid(STATUS.GOOD, bidObj); + bid = Object.assign(bid, {bidderCode: bidCode, + cpm: price, + width: decision.width, + height: decision.height, + ad: retrieveAd(decision)}) + } else { + bid = noBid(bidObj); + } + } else { + bid = noBid(bidObj); + } + addBidResponse(placementCode, bid); + } + done() + } + }; + + function retrieveAd(decision) { + return decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); + } + + function getSize(sizes) { + let width = 'w'; + let height = 'h'; + const result = []; + sizes.forEach(function(size) { + const index = sizeMap.indexOf(size[width] + 'x' + size[height]); + if (index >= 0) { + result.push(index); + } + }); + return result; + } + + // Export the `callBids` function, so that Prebid.js can execute + // this function when the page asks to send out bid requests. + return Object.assign(this, { + queueSync: baseAdapter.queueSync, + callBids: baseAdapter.callBids, + setBidderCode: baseAdapter.setBidderCode, + type: TYPE + }); +}; + +ServerBidServerAdapter.createNew = function() { + return new ServerBidServerAdapter(); +}; + +adaptermanager.registerBidAdapter(new ServerBidServerAdapter(), bidder); + +module.exports = ServerBidServerAdapter; diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index d53fb0d92db..f8cb2c99400 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,128 +1,97 @@ -var utils = require('src/utils.js'); -var bidmanager = require('src/bidmanager.js'); -var bidfactory = require('src/bidfactory.js'); -var ajax = require('src/ajax.js').ajax; -var adaptermanager = require('src/adaptermanager'); - -const STR_BIDDER_CODE = 'sharethrough'; -const STR_VERSION = '1.2.0'; - -var SharethroughAdapter = function SharethroughAdapter() { - const str = {}; - str.STR_BTLR_HOST = document.location.protocol + '//btlr.sharethrough.com'; - str.STR_BEACON_HOST = document.location.protocol + '//b.sharethrough.com/butler?'; - str.placementCodeSet = {}; - str.ajax = ajax; - - function _callBids(params) { - const bids = params.bids; - - // cycle through bids - for (let i = 0; i < bids.length; i += 1) { - const bidRequest = bids[i]; - str.placementCodeSet[bidRequest.placementCode] = bidRequest; - const scriptUrl = _buildSharethroughCall(bidRequest); - str.ajax(scriptUrl, _createCallback(bidRequest), undefined, {withCredentials: true}); - } - } - - function _createCallback(bidRequest) { - return (bidResponse) => { - _strcallback(bidRequest, bidResponse); - }; - } - - function _buildSharethroughCall(bid) { - const pkey = utils.getBidIdParameter('pkey', bid.params); - - let host = str.STR_BTLR_HOST; - - let url = host + '/header-bid/v1?'; - url = utils.tryAppendQueryString(url, 'bidId', bid.bidId); - url = utils.tryAppendQueryString(url, 'placement_key', pkey); - url = appendEnvFields(url); - - return url; - } +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'sharethrough'; +const VERSION = '2.0.0'; +const STR_ENDPOINT = document.location.protocol + '//btlr.sharethrough.com/header-bid/v1'; + +export const sharethroughAdapterSpec = { + code: BIDDER_CODE, + isBidRequestValid: bid => !!bid.params.pkey && bid.bidder === BIDDER_CODE, + buildRequests: (bidRequests, bidderRequest) => { + return bidRequests.map(bid => { + let query = { + bidId: bid.bidId, + placement_key: bid.params.pkey, + hbVersion: '$prebid.version$', + strVersion: VERSION, + hbSource: 'prebid' + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + query.consent_string = bidderRequest.gdprConsent.consentString; + query.consent_required = bidderRequest.gdprConsent.gdprApplies; + } - function _strcallback(bidObj, bidResponse) { - try { - bidResponse = JSON.parse(bidResponse); - } catch (e) { - _handleInvalidBid(bidObj); - return; + return { + method: 'GET', + url: STR_ENDPOINT, + data: query + }; + }) + }, + interpretResponse: ({ body }, req) => { + if (!body || !Object.keys(body).length || !body.creatives.length) { + return []; } - if (bidResponse.creatives && bidResponse.creatives.length > 0) { - _handleBid(bidObj, bidResponse); - } else { - _handleInvalidBid(bidObj); + const creative = body.creatives[0]; + + return [{ + requestId: req.data.bidId, + width: 0, + height: 0, + cpm: creative.cpm, + creativeId: creative.creative.creative_key, + deal_id: creative.creative.deal_id, + currency: 'USD', + netRevenue: true, + ttl: 360, + ad: generateAd(body, req) + }]; + }, + getUserSyncs: (syncOptions, serverResponses) => { + const syncs = []; + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + serverResponses[0].body.cookieSyncUrls.forEach(url => { + syncs.push({ type: 'image', url: url }); + }); } + return syncs; } - - function _handleBid(bidObj, bidResponse) { - try { - const bidId = bidResponse.bidId; - const bid = bidfactory.createBid(1, bidObj); - bid.bidderCode = STR_BIDDER_CODE; - bid.cpm = bidResponse.creatives[0].cpm; - const size = bidObj.sizes[0]; - bid.width = size[0]; - bid.height = size[1]; - bid.adserverRequestId = bidResponse.adserverRequestId; - str.placementCodeSet[bidObj.placementCode].adserverRequestId = bidResponse.adserverRequestId; - - bid.pkey = utils.getBidIdParameter('pkey', bidObj.params); - - const windowLocation = `str_response_${bidId}`; - const bidJsonString = JSON.stringify(bidResponse); - bid.ad = `
-
- - ` +} + +function generateAd(body, req) { + const strRespId = `str_response_${req.data.bidId}`; + + return ` +
+
+ + + ` - bid.ad += sfpScriptTag; + const sfp_js = document.createElement('script'); + sfp_js.src = "//native.sharethrough.com/assets/sfp.js"; + sfp_js.type = 'text/javascript'; + sfp_js.charset = 'utf-8'; + try { + window.top.document.getElementsByTagName('body')[0].appendChild(sfp_js); + } catch (e) { + console.log(e); + } } - bidmanager.addBidResponse(bidObj.placementCode, bid); - } catch (e) { - _handleInvalidBid(bidObj); - } - } - - function _handleInvalidBid(bidObj) { - const bid = bidfactory.createBid(2, bidObj); - bid.bidderCode = STR_BIDDER_CODE; - bidmanager.addBidResponse(bidObj.placementCode, bid); - } - - function appendEnvFields(url) { - url = utils.tryAppendQueryString(url, 'hbVersion', '$prebid.version$'); - url = utils.tryAppendQueryString(url, 'strVersion', STR_VERSION); - url = utils.tryAppendQueryString(url, 'hbSource', 'prebid'); - - return url; - } - - return { - callBids: _callBids, - str: str, - }; -}; - -adaptermanager.registerBidAdapter(new SharethroughAdapter(), 'sharethrough'); - -module.exports = SharethroughAdapter; + })() + `; +} + +// See https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem +function b64EncodeUnicode(str) { + return btoa( + encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, + function toSolidBytes(match, p1) { + return String.fromCharCode('0x' + p1); + })); +} + +registerBidder(sharethroughAdapterSpec); diff --git a/modules/sharethroughBidAdapter.md b/modules/sharethroughBidAdapter.md new file mode 100644 index 00000000000..8ab44f2a0f2 --- /dev/null +++ b/modules/sharethroughBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: Sharethrough Bidder Adapter +Module Type: Bidder Adapter +Maintainer: jchau@sharethrough.com && cpan@sharethrough.com +``` + +# Description + +Module that connects to Sharethrough's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[1, 1]], // a display size + bids: [ + { + bidder: "sharethrough", + params: { + pkey: 'LuB3vxGGFrBZJa6tifXW4xgK' + } + } + ] + },{ + code: 'test-div', + sizes: [[1, 1]], // a mobile size + bids: [ + { + bidder: "sharethrough", + params: { + pkey: 'LuB3vxGGFrBZJa6tifXW4xgK' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/sigmoidAnalyticsAdapter.js b/modules/sigmoidAnalyticsAdapter.js new file mode 100644 index 00000000000..c8c5cc70c53 --- /dev/null +++ b/modules/sigmoidAnalyticsAdapter.js @@ -0,0 +1,285 @@ +/* Sigmoid Analytics Adapter for prebid.js v1.1.0-pre +Updated : 2018-03-28 */ +import includes from 'core-js/library/fn/array/includes'; +import adapter from 'src/AnalyticsAdapter'; +import CONSTANTS from 'src/constants.json'; +import adaptermanager from 'src/adaptermanager'; + +const utils = require('src/utils'); + +const url = 'https://kinesis.us-east-1.amazonaws.com/'; +const analyticsType = 'endpoint'; + +const auctionInitConst = CONSTANTS.EVENTS.AUCTION_INIT; +const auctionEndConst = CONSTANTS.EVENTS.AUCTION_END; +const bidWonConst = CONSTANTS.EVENTS.BID_WON; +const bidRequestConst = CONSTANTS.EVENTS.BID_REQUESTED; +const bidAdjustmentConst = CONSTANTS.EVENTS.BID_ADJUSTMENT; +const bidResponseConst = CONSTANTS.EVENTS.BID_RESPONSE; + +let initOptions = { publisherIds: [], utmTagData: [], adUnits: [] }; +let bidWon = {options: {}, events: []}; +let eventStack = {options: {}, events: []}; + +let auctionStatus = 'not_started'; + +let localStoragePrefix = 'sigmoid_analytics_'; +let utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; +let utmTimeoutKey = 'utm_timeout'; +let utmTimeout = 60 * 60 * 1000; +let sessionTimeout = 60 * 60 * 1000; +let sessionIdStorageKey = 'session_id'; +let sessionTimeoutKey = 'session_timeout'; + +function getParameterByName(param) { + let vars = {}; + window.location.href.replace(location.hash, '').replace( + /[?&]+([^=&]+)=?([^&]*)?/gi, + function(m, key, value) { + vars[key] = value !== undefined ? value : ''; + } + ); + + return vars[param] ? vars[param] : ''; +} + +function buildSessionIdLocalStorageKey() { + return localStoragePrefix.concat(sessionIdStorageKey); +} + +function buildSessionIdTimeoutLocalStorageKey() { + return localStoragePrefix.concat(sessionTimeoutKey); +} + +function updateSessionId() { + if (isSessionIdTimeoutExpired()) { + let newSessionId = utils.generateUUID(); + localStorage.setItem(buildSessionIdLocalStorageKey(), newSessionId); + } + initOptions.sessionId = getSessionId(); + updateSessionIdTimeout(); +} + +function updateSessionIdTimeout() { + localStorage.setItem(buildSessionIdTimeoutLocalStorageKey(), Date.now()); +} + +function isSessionIdTimeoutExpired() { + let cpmSessionTimestamp = localStorage.getItem(buildSessionIdTimeoutLocalStorageKey()); + return Date.now() - cpmSessionTimestamp > sessionTimeout; +} + +function getSessionId() { + return localStorage.getItem(buildSessionIdLocalStorageKey()) ? localStorage.getItem(buildSessionIdLocalStorageKey()) : ''; +} + +function updateUtmTimeout() { + localStorage.setItem(buildUtmLocalStorageTimeoutKey(), Date.now()); +} + +function isUtmTimeoutExpired() { + let utmTimestamp = localStorage.getItem(buildUtmLocalStorageTimeoutKey()); + return (Date.now() - utmTimestamp) > utmTimeout; +} + +function buildUtmLocalStorageTimeoutKey() { + return localStoragePrefix.concat(utmTimeoutKey); +} + +function buildUtmLocalStorageKey(utmMarkKey) { + return localStoragePrefix.concat(utmMarkKey); +} + +function checkOptions() { + if (typeof initOptions.publisherIds === 'undefined') { + return false; + } + + return initOptions.publisherIds.length > 0; +} + +function checkAdUnitConfig() { + if (typeof initOptions.adUnits === 'undefined') { + return false; + } + + return initOptions.adUnits.length > 0; +} + +function buildBidWon(eventType, args) { + bidWon.options = initOptions; + if (checkAdUnitConfig()) { + if (includes(initOptions.adUnits, args.adUnitCode)) { + bidWon.events = [{ args: args, eventType: eventType }]; + } + } else { + bidWon.events = [{ args: args, eventType: eventType }]; + } +} + +function buildEventStack() { + eventStack.options = initOptions; +} + +function filterBidsByAdUnit(bids) { + var filteredBids = []; + bids.forEach(function (bid) { + if (includes(initOptions.adUnits, bid.placementCode)) { + filteredBids.push(bid); + } + }); + return filteredBids; +} + +function isValidEvent(eventType, adUnitCode) { + if (checkAdUnitConfig()) { + let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; + if (!includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType)) { + return false; + } + } + return true; +} + +function isValidEventStack() { + if (eventStack.events.length > 0) { + return eventStack.events.some(function(event) { + return bidRequestConst === event.eventType || bidWonConst === event.eventType; + }); + } + return false; +} + +function isValidBidWon() { + return bidWon.events.length > 0; +} + +function flushEventStack() { + eventStack.events = []; +} + +let sigmoidAdapter = Object.assign(adapter({url, analyticsType}), + { + track({eventType, args}) { + if (!checkOptions()) { + return; + } + + let info = Object.assign({}, args); + + if (info && info.ad) { + info.ad = ''; + } + + if (eventType === auctionInitConst) { + auctionStatus = 'started'; + } + + if (eventType === bidWonConst && auctionStatus === 'not_started') { + updateSessionId(); + buildBidWon(eventType, info); + if (isValidBidWon()) { + send(eventType, bidWon, 'bidWon'); + } + return; + } + + if (eventType === auctionEndConst) { + updateSessionId(); + buildEventStack(); + if (isValidEventStack()) { + send(eventType, eventStack, 'eventStack'); + } + auctionStatus = 'not_started'; + } else { + pushEvent(eventType, info); + } + }, + + }); + +sigmoidAdapter.originEnableAnalytics = sigmoidAdapter.enableAnalytics; + +sigmoidAdapter.enableAnalytics = function (config) { + initOptions = config.options; + initOptions.utmTagData = this.buildUtmTagData(); + utils.logInfo('Sigmoid Analytics enabled with config', initOptions); + sigmoidAdapter.originEnableAnalytics(config); +}; + +sigmoidAdapter.buildUtmTagData = function () { + let utmTagData = {}; + let utmTagsDetected = false; + utmTags.forEach(function(utmTagKey) { + let utmTagValue = getParameterByName(utmTagKey); + if (utmTagValue !== '') { + utmTagsDetected = true; + } + utmTagData[utmTagKey] = utmTagValue; + }); + utmTags.forEach(function(utmTagKey) { + if (utmTagsDetected) { + localStorage.setItem(buildUtmLocalStorageKey(utmTagKey), utmTagData[utmTagKey]); + updateUtmTimeout(); + } else { + if (!isUtmTimeoutExpired()) { + utmTagData[utmTagKey] = localStorage.getItem(buildUtmLocalStorageKey(utmTagKey)) ? localStorage.getItem(buildUtmLocalStorageKey(utmTagKey)) : ''; + updateUtmTimeout(); + } + } + }); + return utmTagData; +}; + +function send(eventType, data, sendDataType) { + AWS.config.credentials = new AWS.Credentials({ + accessKeyId: 'accesskey', secretAccessKey: 'secretkey' + }); + + AWS.config.region = 'us-east-1'; + AWS.config.credentials.get(function(err) { + // attach event listener + if (err) { + utils.logError(err); + return; + } + // create kinesis service object + var kinesis = new AWS.Kinesis({ + apiVersion: '2013-12-02' + }); + var dataList = []; + var jsonData = {}; + jsonData['Data'] = JSON.stringify(data) + '\n'; + jsonData['PartitionKey'] = 'partition-' + Math.random().toString(36).substring(7); + dataList.push(jsonData); + kinesis.putRecords({ + Records: dataList, + StreamName: 'sample-stream' + }); + if (sendDataType === 'eventStack') { + flushEventStack(); + } + }); +}; + +function pushEvent(eventType, args) { + if (eventType === bidRequestConst) { + if (checkAdUnitConfig()) { + args.bids = filterBidsByAdUnit(args.bids); + } + if (args.bids.length > 0) { + eventStack.events.push({ eventType: eventType, args: args }); + } + } else { + if (isValidEvent(eventType, args.adUnitCode)) { + eventStack.events.push({ eventType: eventType, args: args }); + } + } +} + +adaptermanager.registerAnalyticsAdapter({ + adapter: sigmoidAdapter, + code: 'sigmoid' +}); + +export default sigmoidAdapter; diff --git a/modules/sigmoidAnalyticsAdapter.md b/modules/sigmoidAnalyticsAdapter.md new file mode 100644 index 00000000000..8ff46c7f2be --- /dev/null +++ b/modules/sigmoidAnalyticsAdapter.md @@ -0,0 +1,23 @@ +# Overview +Module Name: Sigmoid Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: ramees@sigmoidanalytics.com + +# Description + +Analytics adapter for Sigmoid. We are an advanced analytical solutions company. +https://www.sigmoid.com/ + +# Test Parameters + +``` +{ + provider: 'sigmoid', + options : { + publisherIds: ["3gxdf18d32"] + } +} + +``` diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index d815f69c752..7db4747927a 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -1,60 +1,114 @@ -var utils = require('src/utils.js'); -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader.js'); -var url = require('src/url.js'); -var adaptermanager = require('src/adaptermanager'); +import * as utils from 'src/utils'; +import { + config +} from 'src/config'; +import { + registerBidder +} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'smartadserver'; +export const spec = { + code: BIDDER_CODE, + aliases: ['smart'], // 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 && bid.params.siteId && bid.params.pageId && bid.params.formatId && bid.params.domain); + }, + /** + * 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) { + // use bidderRequest.bids[] to get bidder-dependent request info -var SmartAdServer = function SmartAdServer() { - var generateCallback = function(bid) { - var callbackId = 'sas_' + utils.getUniqueIdentifierStr(); - $$PREBID_GLOBAL$$[callbackId] = function(adUnit) { - var bidObject; - if (adUnit) { - utils.logMessage(`[SmartAdServer] bid response for placementCode ${bid.placementCode}`); - bidObject = bidfactory.createBid(1); - bidObject.bidderCode = 'smartadserver'; - bidObject.cpm = adUnit.cpm; - bidObject.currency = adUnit.currency; - bidObject.ad = adUnit.ad; - bidObject.width = adUnit.width; - bidObject.height = adUnit.height; - bidObject.dealId = adUnit.dealId; - bidmanager.addBidResponse(bid.placementCode, bidObject); - } else { - utils.logMessage(`[SmartAdServer] no bid response for placementCode ${bid.placementCode}`); - bidObject = bidfactory.createBid(2); - bidObject.bidderCode = 'smartadserver'; - bidmanager.addBidResponse(bid.placementCode, bidObject); - } - }; - return callbackId; - }; + // if your bidder supports multiple currencies, use config.getConfig(currency) + // to find which one the ad server needs - return { - callBids: function(params) { - for (var i = 0; i < params.bids.length; i++) { - var bid = params.bids[i]; - var adCall = url.parse(bid.params.domain); - adCall.pathname = '/prebid'; - adCall.search = { - 'pbjscbk': '$$PREBID_GLOBAL$$.' + generateCallback(bid), - 'siteid': bid.params.siteId, - 'pgid': bid.params.pageId, - 'fmtid': bid.params.formatId, - 'ccy': bid.params.currency || 'USD', - 'bidfloor': bid.params.bidfloor || 0.0, - 'tgt': encodeURIComponent(bid.params.target || ''), - 'tag': bid.placementCode, - 'sizes': bid.sizes.map(size => size[0] + 'x' + size[1]).join(','), - 'async': 1 + // pull requested transaction ID from bidderRequest.bids[].transactionId + return validBidRequests.map(bid => { + var payload = { + siteid: bid.params.siteId, + pageid: bid.params.pageId, + formatid: bid.params.formatId, + currencyCode: config.getConfig('currency.adServerCurrency'), + bidfloor: bid.params.bidfloor || 0.0, + targeting: bid.params.target && bid.params.target != '' ? bid.params.target : undefined, + buid: bid.params.buId && bid.params.buId != '' ? bid.params.buId : undefined, + appname: bid.params.appName && bid.params.appName != '' ? bid.params.appName : undefined, + ckid: bid.params.ckId || 0, + tagId: bid.adUnitCode, + sizes: bid.sizes.map(size => ({ + w: size[0], + h: size[1] + })), + pageDomain: utils.getTopWindowUrl(), + transactionId: bid.transactionId, + timeout: config.getConfig('bidderTimeout'), + bidId: bid.bidId, + prebidVersion: '$prebid.version$' + }; + var payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: bid.params.domain + '/prebid/v1', + 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 = []; + var response = serverResponse.body; + try { + if (response) { + const bidResponse = { + requestId: JSON.parse(bidRequest.data).bidId, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creativeId, + dealId: response.dealId, + currency: response.currency, + netRevenue: response.isNetCpm, + ttl: response.ttl, + referrer: utils.getTopWindowUrl(), + adUrl: response.adUrl, + ad: response.ad }; - adloader.loadScript(url.format(adCall)); + bidResponses.push(bidResponse); } + } catch (error) { + utils.logError('Error while parsing smart server response', error); } - }; -}; - -adaptermanager.registerBidAdapter(new SmartAdServer(), 'smartadserver'); - -module.exports = SmartAdServer; + return bidResponses; + }, + /** + * User syncs. + * + * @param {*} syncOptions Publisher prebid configuration. + * @param {*} serverResponses A successful response from the server. + * @return {Syncs[]} An array of syncs that should be executed. + */ + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = [] + if (syncOptions.iframeEnabled && serverResponses.length > 0) { + syncs.push({ + type: 'iframe', + url: serverResponses[0].body.cSyncUrl + }); + } + return syncs; + } +} +registerBidder(spec); diff --git a/modules/smartadserverBidAdapter.md b/modules/smartadserverBidAdapter.md new file mode 100644 index 00000000000..1200c0961a0 --- /dev/null +++ b/modules/smartadserverBidAdapter.md @@ -0,0 +1,62 @@ +# Overview + +``` +Module Name: Smart Ad Server Bidder Adapter +Module Type: Bidder Adapter +Maintainer: gcarnec@smartadserver.com +``` + +# Description + +Connect to Smart for bids. + +The Smart adapter requires setup and approval from the Smart team. +Please reach out to your Technical account manager for more information. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "smart", + params: { + domain: 'http://ww251.smartadserver.com', + siteId: 207435, + pageId: 896536, + formatId: 62913, + ckId: 1122334455 // optional + } + } + ] + } + ]; +``` + +## In-app +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "smart", + params: { + domain: 'http://ww251.smartadserver.com', + siteId: 207435, + pageId: 896536, + formatId: 65906, + buId: "com.smartadserver.android.dashboard", // in-app only + appName: "Smart AdServer Preview", // in-app only + ckId: 1122334455 // optional + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index 89f93f393c7..0c553b567ef 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -1,180 +1,91 @@ -import Adapter from 'src/adapter'; -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes'; import * as utils from 'src/utils'; -import {ajax} from 'src/ajax'; -import {STATUS} from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; -const SMARTYADS_BIDDER_CODE = 'smartyads'; +const BIDDER_CODE = 'smartyads'; +const URL = '//ssp-nj.webtradehub.com/?c=o&m=multi'; +const URL_SYNC = '//ssp-nj.webtradehub.com/?c=o&m=cookie'; -var sizeMap = { - 1: '468x60', - 2: '728x90', - 8: '120x600', - 9: '160x600', - 10: '300x600', - 15: '300x250', - 16: '336x280', - 19: '300x100', - 43: '320x50', - 44: '300x50', - 48: '300x300', - 54: '300x1050', - 55: '970x90', - 57: '970x250', - 58: '1000x90', - 59: '320x80', - 61: '1000x1000', - 65: '640x480', - 67: '320x480', - 68: '1800x1000', - 72: '320x320', - 73: '320x160', - 83: '480x300', - 94: '970x310', - 96: '970x210', - 101: '480x320', - 102: '768x1024', - 113: '1000x300', - 117: '320x100', - 125: '800x250', - 126: '200x600' -}; - -utils._each(sizeMap, (item, key) => sizeMap[item] = key); - -function SmartyadsAdapter() { - function _callBids(bidderRequest) { - var bids = bidderRequest.bids || []; - - bids.forEach((bid) => { - try { - ajax(buildOptimizedCall(bid), bidCallback, undefined, { withCredentials: true }); - } catch (err) { - utils.logError('Error sending smartyads request for placement code ' + bid.placementCode, null, err); - } - - function bidCallback(responseText) { - try { - utils.logMessage('XHR callback function called for ad ID: ' + bid.bidId); - handleRpCB(responseText, bid); - } catch (err) { - if (typeof err === 'string') { - utils.logWarn(`${err} when processing smartyads response for placement code ${bid.placementCode}`); - } else { - utils.logError('Error processing smartyads response for placement code ' + bid.placementCode, null, err); - } - - // indicate that there is no bid for this placement - let badBid = bidfactory.createBid(STATUS.NO_BID, bid); - badBid.bidderCode = bid.bidder; - badBid.error = err; - bidmanager.addBidResponse(bid.placementCode, badBid); - } - } - }); - } - - function buildOptimizedCall(bid) { - bid.startTime = new Date().getTime(); - - // use smartyads sizes if provided, otherwise adUnit.sizes - var parsedSizes = SmartyadsAdapter.masSizeOrdering( - Array.isArray(bid.params.sizes) ? bid.params.sizes.map(size => (sizeMap[size] || '').split('x')) : bid.sizes - ); - - if (parsedSizes.length < 1) { - throw 'no valid sizes'; - } - - var secure; - if (window.location.protocol !== 'http:') { - secure = 1; - } else { - secure = 0; - } - - const host = window.location.host; - const page = window.location.pathname; - const language = navigator.language; - const deviceWidth = window.screen.width; - const deviceHeight = window.screen.height; - - var queryString = [ - 'banner_id', bid.params.banner_id, - 'size_ad', parsedSizes[0], - 'alt_size_ad', parsedSizes.slice(1).join(',') || undefined, - 'host', host, - 'page', page, - 'language', language, - 'deviceWidth', deviceWidth, - 'deviceHeight', deviceHeight, - 'secure', secure, - 'bidId', bid.bidId, - 'checkOn', 'rf' - ]; - - return queryString.reduce( - (memo, curr, index) => - index % 2 === 0 && queryString[index + 1] !== undefined - ? memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' - : memo, - '//ssp-nj.webtradehub.com/?' - ).slice(0, -1); +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; } - - function handleRpCB(responseText, bidRequest) { - let ad = JSON.parse(responseText); // can throw - - var bid = bidfactory.createBid(STATUS.GOOD, bidRequest); - bid.creative_id = ad.ad_id; - bid.bidderCode = bidRequest.bidder; - bid.cpm = ad.cpm || 0; - bid.ad = ad.adm; - bid.width = ad.width; - bid.height = ad.height; - bid.dealId = ad.deal; - - bidmanager.addBidResponse(bidRequest.placementCode, bid); + 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; } - - return Object.assign(new Adapter(SMARTYADS_BIDDER_CODE), { // SMARTYADS_BIDDER_CODE smartyads - callBids: _callBids - }); } -SmartyadsAdapter.masSizeOrdering = function (sizes) { - const MAS_SIZE_PRIORITY = [15, 2, 9]; - - return utils.parseSizesInput(sizes) - // map sizes while excluding non-matches - .reduce((result, size) => { - let mappedSize = parseInt(sizeMap[size], 10); - if (mappedSize) { - result.push(mappedSize); - } - return result; - }, []) - .sort((first, second) => { - // sort by MAS_SIZE_PRIORITY priority order - const firstPriority = MAS_SIZE_PRIORITY.indexOf(first); - const secondPriority = MAS_SIZE_PRIORITY.indexOf(second); - - if (firstPriority > -1 || secondPriority > -1) { - if (firstPriority === -1) { - return 1; - } - if (secondPriority === -1) { - return -1; - } - return firstPriority - secondPriority; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placementId)); + }, + + buildRequests: (validBidRequests = []) => { + let winTop = window; + try { + window.top.location.toString(); + winTop = window.top; + } catch (e) { + utils.logMessage(e); + } + let location = utils.getTopWindowLocation(); + let placements = []; + let request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language : '', + 'secure': location.protocol === 'https:' ? 1 : 0, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + let bid = validBidRequests[i]; + placements.push({ + placementId: bid.params.placementId, + bidId: bid.bidId, + traffic: bid.params.traffic || BANNER + }); + } + return { + method: 'POST', + url: URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + serverResponse = serverResponse.body; + for (let i = 0; i < serverResponse.length; i++) { + let resItem = serverResponse[i]; + if (isBidResponseValid(resItem)) { + delete resItem.mediaType; + response.push(resItem); } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses) => { + return [{ + type: 'image', + url: URL_SYNC + }]; + } - return first - second; - }); }; -adaptermanager.registerBidAdapter(new SmartyadsAdapter(), 'smartyads'); - -module.exports = SmartyadsAdapter; +registerBidder(spec); diff --git a/modules/smartyadsBidAdapter.md b/modules/smartyadsBidAdapter.md new file mode 100644 index 00000000000..5102a6fd128 --- /dev/null +++ b/modules/smartyadsBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +``` +Module Name: SmartyAds Bidder Adapter +Module Type: Bidder Adapter +Maintainer: supply@smartyads.com +``` + +# Description + +Module that connects to SmartyAds' demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'placementId_0', + sizes: [[300, 250]], + bids: [{ + bidder: 'smartyads', + params: { + placementId: 0, + traffic: 'banner' + } + }] + } + ]; +``` \ No newline at end of file diff --git a/modules/somoaudienceBidAdapter.js b/modules/somoaudienceBidAdapter.js new file mode 100644 index 00000000000..3c5d9854426 --- /dev/null +++ b/modules/somoaudienceBidAdapter.js @@ -0,0 +1,117 @@ +import {getTopWindowReferrer, getTopWindowLocation} from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +export const spec = { + + code: 'somoaudience', + + aliases: ['somo'], + + isBidRequestValid: bid => ( + !!(bid && bid.params && bid.params.placementId) + ), + + buildRequests: function(bidRequests) { + return bidRequests.map(bidRequest => { + return { + method: 'POST', + url: '//publisher-east.mobileadtrading.com/rtb/bid?s=' + bidRequest.params.placementId.toString(), + data: openRtbRequest(bidRequest), + bidRequest: bidRequest + }; + }); + }, + + interpretResponse(response, request) { + return bidResponseAvailable(request, response); + } +}; + +function bidResponseAvailable(bidRequest, bidResponse) { + let bidResponses = []; + let bidId = 1; + if (typeof bidRequest != 'undefined' && typeof bidRequest.bidRequest != 'undefined' && typeof bidRequest.bidRequest.bidId != 'undefined') { + bidId = bidRequest.bidRequest.bidId; + } + if (bidResponse.body) { + let bidData = bidResponse.body.seatbid[0].bid[0]; + const bid = { + requestId: bidId, + cpm: bidData.price, + width: bidData.w, + height: bidData.h, + ad: bidData.adm, + ttl: 360, + creativeId: bidData.crid, + adId: bidId, + netRevenue: false, + currency: 'USD', + }; + bidResponses.push(bid); + } + return bidResponses; +} + +function openRtbRequest(bidRequest) { + return { + id: bidRequest.bidderRequestId, + imp: [openRtbImpression(bidRequest)], + at: 1, + tmax: 400, + site: openRtbSite(bidRequest), + app: openRtbApp(bidRequest), + device: openRtbDevice() + }; +} + +function openRtbImpression(bidRequest) { + return { + id: bidRequest.bidId, + banner: {} + }; +} + +function isApp(bidRequest) { + if (bidRequest.params.app) { + return true; + } else { + return false; + } +} + +function openRtbSite(bidRequest) { + if (!isApp(bidRequest)) { + const pageUrl = getTopWindowLocation().href; + const domain = getTopWindowLocation().hostname; + return { + ref: getTopWindowReferrer(), + page: pageUrl, + domain: domain + } + } else { + return null; + } +} + +function openRtbApp(bidRequest) { + if (isApp(bidRequest)) { + const appParams = bidRequest.params.app; + return { + bundle: appParams.bundle ? appParams.bundle : null, + storeurl: appParams.storeUrl ? appParams.storeUrl : null, + domain: appParams.domain ? appParams.domain : null, + name: appParams.name ? appParams.name : null, + } + } else { + return null; + } +} + +function openRtbDevice() { + return { + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + }; +} + +registerBidder(spec); diff --git a/modules/somoaudienceBidAdapter.md b/modules/somoaudienceBidAdapter.md new file mode 100644 index 00000000000..a622d73d84b --- /dev/null +++ b/modules/somoaudienceBidAdapter.md @@ -0,0 +1,39 @@ +# Overview + +**Module Name**: Somo Audience Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@somoaudience.com +# Description +Connects to Somo Audience demand source. +Please use ```somoaudience``` as the bidder code. +# Test Site Parameters +``` + var adUnits = [{ + code: 'banner-ad-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'somoaudience', + params: { + placementId: '22a58cfb0c9b656bff713d1236e930e8' + } + }] + }]; +``` +# Test App Parameters +``` +var adUnits = [{ + code: 'banner-ad-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'somoaudience', + params: { + placementId: '22a58cfb0c9b656bff713d1236e930e8', + app: { + bundle: 'com.somoaudience.apps', + storeUrl: 'http://somoaudience.com/apps', + domain: 'somoaudience.com', + } + } + }] +}]; +``` diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 81745427742..a8b5bd13e05 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,118 +1,217 @@ -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader.js'); -var utils = require('src/utils'); -var adaptermanager = require('src/adaptermanager'); - -var SonobiAdapter = function SonobiAdapter() { - var keymakerAssoc = {}; // Remember placement codes for callback mapping - var bidReqAssoc = {}; // Remember bids for bid complete reporting - - function _phone_in(request) { - var trinity = 'https://apex.go.sonobi.com/trinity.js?key_maker='; - var adSlots = request.bids || []; - var bidderRequestId = request.bidderRequestId; - var ref = (window.frameElement) ? '&ref=' + encodeURI(top.location.host || document.referrer) : ''; - adloader.loadScript(trinity + JSON.stringify(_keymaker(adSlots)) + '&cv=' + _operator(bidderRequestId) + ref); - } +import { registerBidder } from 'src/adapters/bidderFactory'; +import { getTopWindowLocation, parseSizesInput, logError, generateUUID, deepAccess, isEmpty } from '../src/utils'; +import { BANNER, VIDEO } from '../src/mediaTypes'; +import find from 'core-js/library/fn/array/find'; - function _keymaker(adSlots) { - var keyring = {}; - utils._each(adSlots, function(bidRequest) { - if (bidRequest.params) { - // Optional - var floor = (bidRequest.params.floor) ? bidRequest.params.floor : null; - // Mandatory - var slotIdentifier = (bidRequest.params.ad_unit) ? bidRequest.params.ad_unit : (bidRequest.params.placement_id) ? bidRequest.params.placement_id : null; - var sizes = (bidRequest.params.sizes) ? bidRequest.params.sizes : bidRequest.sizes || null; - sizes = utils.parseSizesInput(sizes).toString(); - - if (utils.isEmpty(sizes)) { - utils.logError('Sonobi adapter expects sizes for ' + bidRequest.placementCode); - } +const BIDDER_CODE = 'sonobi'; +const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; +const PAGEVIEW_ID = generateUUID(); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid - The bid params to validate. + * @return {boolean} True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: bid => !!(bid.params && (bid.params.ad_unit || bid.params.placement_id) && (bid.params.sizes || bid.sizes)), - var bidId = bidRequest.bidId; - - var args = (sizes) ? ((floor) ? (sizes + '|f=' + floor) : (sizes)) : (floor) ? ('f=' + floor) : ''; - if (/^[\/]?[\d]+[[\/].+[\/]?]?$/.test(slotIdentifier)) { - slotIdentifier = slotIdentifier.charAt(0) === '/' ? slotIdentifier : '/' + slotIdentifier; - keyring[slotIdentifier + '|' + bidId] = args; - keymakerAssoc[slotIdentifier + '|' + bidId] = bidRequest.placementCode; - bidReqAssoc[bidRequest.placementCode] = bidRequest; - } else if (/^[0-9a-fA-F]{20}$/.test(slotIdentifier) && slotIdentifier.length === 20) { - keyring[bidId] = slotIdentifier + '|' + args; - keymakerAssoc[bidId] = bidRequest.placementCode; - bidReqAssoc[bidRequest.placementCode] = bidRequest; - } else { - keymakerAssoc[bidId] = bidRequest.placementCode; - bidReqAssoc[bidRequest.placementCode] = bidRequest; - _failure(bidRequest.placementCode); - utils.logError('The ad unit code or Sonobi Placement id for slot ' + bidRequest.placementCode + ' is invalid'); + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @return {object} ServerRequest - Info describing the request to the server. + */ + buildRequests: (validBidRequests) => { + const bids = validBidRequests.map(bid => { + let slotIdentifier = _validateSlot(bid); + if (/^[\/]?[\d]+[[\/].+[\/]?]?$/.test(slotIdentifier)) { + slotIdentifier = slotIdentifier.charAt(0) === '/' ? slotIdentifier : '/' + slotIdentifier; + return { + [`${slotIdentifier}|${bid.bidId}`]: `${_validateSize(bid)}${_validateFloor(bid)}` } + } else if (/^[0-9a-fA-F]{20}$/.test(slotIdentifier) && slotIdentifier.length === 20) { + return { + [bid.bidId]: `${slotIdentifier}|${_validateSize(bid)}${_validateFloor(bid)}` + } + } else { + logError(`The ad unit code or Sonobi Placement id for slot ${bid.bidId} is invalid`); } }); - return keyring; - } - function _operator(bidderRequestId) { - var cb_name = 'sbi_' + bidderRequestId; - window[cb_name] = _trinity; - return cb_name; - } + let data = {}; + bids.forEach((bid) => { Object.assign(data, bid); }); - function _trinity(response) { - var slots = response.slots || {}; - var sbi_dc = response.sbi_dc || ''; - utils._each(slots, function(bid, slot_id) { - var placementCode = keymakerAssoc[slot_id]; + const payload = { + 'key_maker': JSON.stringify(data), + 'ref': getTopWindowLocation().host, + 's': generateUUID(), + 'pv': PAGEVIEW_ID, + 'vp': _getPlatform(), + 'lib_name': 'prebid', + 'lib_v': '$prebid.version$' + }; + + if (validBidRequests[0].params.hfa) { + payload.hfa = validBidRequests[0].params.hfa; + } + if (validBidRequests[0].params.referrer) { + payload.ref = validBidRequests[0].params.referrer; + } + + // If there is no key_maker data, then dont make the request. + if (isEmpty(data)) { + return null; + } + + return { + method: 'GET', + url: STR_ENDPOINT, + withCredentials: true, + data: payload, + bidderRequests: validBidRequests + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {*} bidderRequests - Info describing the request to the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse, { bidderRequests }) => { + const bidResponse = serverResponse.body; + const bidsReturned = []; + + if (Object.keys(bidResponse.slots).length === 0) { + return bidsReturned; + } + + Object.keys(bidResponse.slots).forEach(slot => { + const bidId = _getBidIdFromTrinityKey(slot); + const bidRequest = find(bidderRequests, bidReqest => bidReqest.bidId === bidId); + const videoMediaType = deepAccess(bidRequest, 'mediaTypes.video'); + const mediaType = bidRequest.mediaType || (videoMediaType ? 'video' : null); + const createCreative = _creative(mediaType); + const bid = bidResponse.slots[slot]; if (bid.sbi_aid && bid.sbi_mouse && bid.sbi_size) { - _success(placementCode, sbi_dc, bid); - } else { - _failure(placementCode); + const [ + width = 1, + height = 1 + ] = bid.sbi_size.split('x'); + const bids = { + requestId: bidId, + cpm: Number(bid.sbi_mouse), + width: Number(width), + height: Number(height), + ad: createCreative(bidResponse.sbi_dc, bid.sbi_aid), + ttl: 500, + creativeId: bid.sbi_aid, + netRevenue: true, + currency: 'USD' + }; + + if (bid.sbi_dozer) { + bids.dealId = bid.sbi_dozer; + } + + const creativeType = bid.sbi_ct; + if (creativeType && (creativeType === 'video' || creativeType === 'outstream')) { + bids.mediaType = 'video'; + bids.vastUrl = createCreative(bidResponse.sbi_dc, bid.sbi_aid); + delete bids.ad; + delete bids.width; + delete bids.height; + } + bidsReturned.push(bids); } - delete keymakerAssoc[slot_id]; }); + return bidsReturned; + }, + /** + * Register User Sync. + */ + getUserSyncs: (syncOptions, serverResponses) => { + const syncs = []; + try { + if (syncOptions.pixelEnabled) { + serverResponses[0].body.sbi_px.forEach(pixel => { + syncs.push({ + type: pixel.type, + url: pixel.url + }); + }); + } + } catch (e) { + logError(e) + } + return syncs; } +}; - function _seraph(placementCode) { - var theOne = bidReqAssoc[placementCode]; - delete bidReqAssoc[placementCode]; - return theOne; +function _validateSize (bid) { + if (bid.params.sizes) { + return parseSizesInput(bid.params.sizes).join(','); } + return parseSizesInput(bid.sizes).join(','); +} - function _success(placementCode, sbi_dc, bid) { - var goodBid = bidfactory.createBid(1, _seraph(placementCode)); - if (bid.sbi_dozer) { - goodBid.dealId = bid.sbi_dozer; - } - goodBid.bidderCode = 'sonobi'; - goodBid.ad = _creative(sbi_dc, bid.sbi_aid); - goodBid.cpm = Number(bid.sbi_mouse); - goodBid.width = Number(bid.sbi_size.split('x')[0]) || 1; - goodBid.height = Number(bid.sbi_size.split('x')[1]) || 1; - bidmanager.addBidResponse(placementCode, goodBid); +function _validateSlot (bid) { + if (bid.params.ad_unit) { + return bid.params.ad_unit; } + return bid.params.placement_id; +} - function _failure(placementCode) { - var failBid = bidfactory.createBid(2, _seraph(placementCode)); - failBid.bidderCode = 'sonobi'; - bidmanager.addBidResponse(placementCode, failBid); +function _validateFloor (bid) { + if (bid.params.floor) { + return `|f=${bid.params.floor}`; } + return ''; +} - function _creative(sbi_dc, sbi_aid) { - var src = 'https://' + sbi_dc + 'apex.go.sonobi.com/sbi.js?aid=' + sbi_aid + '&as=null'; - return ''; +const _creative = (mediaType) => (sbi_dc, sbi_aid) => { + if (mediaType === 'video') { + return _videoCreative(sbi_dc, sbi_aid) } - - return { - callBids: _phone_in, - formRequest: _keymaker, - parseResponse: _trinity, - success: _success, - failure: _failure - }; + const src = 'https://' + sbi_dc + 'apex.go.sonobi.com/sbi.js?aid=' + sbi_aid + '&as=null' + '&ref=' + getTopWindowLocation().host; + return ''; }; -adaptermanager.registerBidAdapter(new SonobiAdapter(), 'sonobi'); +function _videoCreative(sbi_dc, sbi_aid) { + return `https://${sbi_dc}apex.go.sonobi.com/vast.xml?vid=${sbi_aid}&ref=${getTopWindowLocation().host}` +} + +function _getBidIdFromTrinityKey (key) { + return key.split('|').slice(-1)[0] +} + +/** + * @param context - the window to determine the innerWidth from. This is purely for test purposes as it should always be the current window + */ +export const _isInbounds = (context = window) => (lowerBound = 0, upperBound = Number.MAX_SAFE_INTEGER) => context.innerWidth >= lowerBound && context.innerWidth < upperBound; + +/** + * @param context - the window to determine the innerWidth from. This is purely for test purposes as it should always be the current window + */ +export function _getPlatform(context = window) { + const isInBounds = _isInbounds(context); + const MOBILE_VIEWPORT = { + lt: 768 + }; + const TABLET_VIEWPORT = { + lt: 992, + ge: 768 + }; + if (isInBounds(0, MOBILE_VIEWPORT.lt)) { + return 'mobile' + } + if (isInBounds(TABLET_VIEWPORT.ge, TABLET_VIEWPORT.lt)) { + return 'tablet' + } + return 'desktop'; +} -module.exports = SonobiAdapter; +registerBidder(spec); diff --git a/modules/sonobiBidAdapter.md b/modules/sonobiBidAdapter.md new file mode 100644 index 00000000000..cc4dd8733d4 --- /dev/null +++ b/modules/sonobiBidAdapter.md @@ -0,0 +1,70 @@ +# Overview + +``` +Module Name: Sonobi Bidder Adapter +Module Type: Bidder Adapter +Maintainer: apex.prebid@sonobi.com +``` + +# Description + +Module that connects to Sonobi's demand sources. + +# Test Parameters +``` + var adUnits = [ + { + code: 'adUnit_af', + sizes: [[300, 250], [300, 600]], // a display size + bids: [ + { + bidder: 'sonobi', + params: { + ad_unit: '/7780971/sparks_prebid_MR', + placement_id: '1a2b3c4d5e6f1a2b3c4d', // ad_unit and placement_id are mutually exclusive + sizes: [[300, 250], [300, 600]], + floor: 1 // optional + } + } + ] + } + ]; +``` + +# Video Test Parameters +``` + var videoAdUnit = { + code: 'adUnit_af', + sizes: [640,480], + mediaTypes: { + video: {context: 'instream'} + }, + bids: [ + { + bidder: 'sonobi', + params: { + placement_id: '92e95368e86639dbd86d', + } + } + ] + }; +``` + +Example bidsBackHandler for video bids +``` +pbjs.requestBids({ + timeout : 700, + bidsBackHandler : function(bids) { + var videoUrl = pbjs.adServers.dfp.buildVideoUrl({ + adUnit: videoAdUnit, + params: { + cust_params: { + hb_vid: bids.adUnit_af.bids[0].creativeId + }, + iu: '/7780971/apex_jwplayer_video' + } + }); + invokeVideoPlayer(videoUrl); + } + }); +``` diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index a2fef49eaed..6d67288cf09 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -1,156 +1,98 @@ -var CONSTANTS = require('src/constants.json'); -var utils = require('src/utils.js'); -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader'); -var adaptermanager = require('src/adaptermanager'); - -/** - * Adapter for requesting bids from Sovrn - */ -var SovrnAdapter = function SovrnAdapter() { - var sovrnUrl = 'ap.lijit.com/rtb/bid'; - - function _callBids(params) { - var sovrnBids = params.bids || []; - - _requestBids(sovrnBids); - } - - function _requestBids(bidReqs) { - // build bid request object - var domain = window.location.host; - var page = window.location.pathname + location.search + location.hash; - - var sovrnImps = []; - - // build impression array for sovrn +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; +import { REPO_AND_VERSION } from 'src/constants'; + +export const spec = { + code: 'sovrn', + supportedMediaTypes: [BANNER], + + /** + * Check if the bid is a valid zone ID in either number or string form + * @param {object} bid the Sovrn bid to validate + * @return boolean for whether or not a bid is valid + */ + isBidRequestValid: function(bid) { + return !!(bid.params.tagid && !isNaN(parseFloat(bid.params.tagid)) && isFinite(bid.params.tagid)); + }, + + /** + * Format the bid request object for our endpoint + * @param {BidRequest[]} bidRequests Array of Sovrn bidders + * @return object of parameters for Prebid AJAX request + */ + buildRequests: function(bidReqs, bidderRequest) { + const loc = utils.getTopWindowLocation(); + let sovrnImps = []; + let iv; utils._each(bidReqs, function (bid) { - var tagId = utils.getBidIdParameter('tagid', bid.params); - var bidFloor = utils.getBidIdParameter('bidfloor', bid.params); - var adW = 0; - var adH = 0; - - // sovrn supports only one size per tagid, so we just take the first size if there are more - // if we are a 2 item array of 2 numbers, we must be a SingleSize array - var bidSizes = Array.isArray(bid.params.sizes) ? bid.params.sizes : bid.sizes; - var sizeArrayLength = bidSizes.length; - if (sizeArrayLength === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { - adW = bidSizes[0]; - adH = bidSizes[1]; - } else { - adW = bidSizes[0][0]; - adH = bidSizes[0][1]; - } - - var imp = - { - id: bid.bidId, - banner: { - w: adW, - h: adH - }, - tagid: tagId, - bidfloor: bidFloor - }; - sovrnImps.push(imp); + iv = iv || utils.getBidIdParameter('iv', bid.params); + sovrnImps.push({ + id: bid.bidId, + banner: { w: 1, h: 1 }, + tagid: String(utils.getBidIdParameter('tagid', bid.params)), + bidfloor: utils.getBidIdParameter('bidfloor', bid.params) + }); }); - - // build bid request with impressions - var sovrnBidReq = { + const sovrnBidReq = { id: utils.getUniqueIdentifierStr(), imp: sovrnImps, site: { - domain: domain, - page: page + domain: loc.host, + page: loc.host + loc.pathname + loc.search + loc.hash } }; - - var scriptUrl = '//' + sovrnUrl + '?callback=window.$$PREBID_GLOBAL$$.sovrnResponse' + - '&src=' + CONSTANTS.REPO_AND_VERSION + - '&br=' + encodeURIComponent(JSON.stringify(sovrnBidReq)); - adloader.loadScript(scriptUrl); - } - - function addBlankBidResponses(impidsWithBidBack) { - var missing = utils.getBidderRequestAllAdUnits('sovrn'); - if (missing) { - missing = missing.bids.filter(bid => impidsWithBidBack.indexOf(bid.bidId) < 0); - } else { - missing = []; + if (iv) sovrnBidReq.iv = iv; + + if (bidderRequest && bidderRequest.gdprConsent) { + sovrnBidReq.regs = { + ext: { + gdpr: +bidderRequest.gdprConsent.gdprApplies + }}; + sovrnBidReq.user = { + ext: { + consent: bidderRequest.gdprConsent.consentString + }}; } - missing.forEach(function (bidRequest) { - // Add a no-bid response for this bid request. - var bid = {}; - bid = bidfactory.createBid(2, bidRequest); - bid.bidderCode = 'sovrn'; - bidmanager.addBidResponse(bidRequest.placementCode, bid); - }); - } - - // expose the callback to the global object: - $$PREBID_GLOBAL$$.sovrnResponse = function (sovrnResponseObj) { - var impidsWithBidBack = []; - - // valid response object from sovrn - if (sovrnResponseObj && sovrnResponseObj.id && sovrnResponseObj.seatbid && sovrnResponseObj.seatbid.length !== 0 && - sovrnResponseObj.seatbid[0].bid && sovrnResponseObj.seatbid[0].bid.length !== 0) { - sovrnResponseObj.seatbid[0].bid.forEach(function (sovrnBid) { - var responseCPM; - var placementCode = ''; - var id = sovrnBid.impid; - var bid = {}; - - var bidObj = utils.getBidRequest(id); - - if (bidObj) { - placementCode = bidObj.placementCode; - bidObj.status = CONSTANTS.STATUS.GOOD; - - responseCPM = parseFloat(sovrnBid.price); - - if (responseCPM !== 0) { - sovrnBid.placementCode = placementCode; - sovrnBid.size = bidObj.sizes; - var responseAd = sovrnBid.adm; - - // build impression url from response - var responseNurl = ''; - - // store bid response - // bid status is good (indicating 1) - bid = bidfactory.createBid(1, bidObj); - bid.creative_id = sovrnBid.id; - bid.bidderCode = 'sovrn'; - bid.cpm = responseCPM; - - // set ad content + impression url - // sovrn returns '; - - return divHtml + script; - }; - - var formatOutstreamHTML = function(bid) { - var placementCode = bid.placementCode; - - 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; - } - - var script = "'; - - return script; - }; - - function formatAdHTML(bid, size) { - var integrationType = bid.params.format; - - var html = ''; - if (integrationType && integrationType !== 'inbanner') { - html = formatOutstreamHTML(bid); - } else { - html = formatInBannerHTML(bid, size); - } - - return html; - } - - function extractPrice(vast) { - var priceData = vast.getPricing(); - - if (!priceData) { - console.warn("freewheel-ssp: Bid pricing Can't be retreived. You may need to enable pricing on you're zone. Please get in touch with your Freewheel contact."); - } - - return priceData; - } - - function formatBidObject(bidRequest, valid, priceData, html, width, height) { - var bidObject; - if (valid && priceData) { - // valid bid response - bidObject = bidfactory.createBid(1, bidRequest); - bidObject.bidderCode = bidRequest.bidder; - bidObject.cpm = priceData.price; - bidObject.currencyCode = priceData.currency; - bidObject.ad = html; - bidObject.width = width; - bidObject.height = height; - } else { - // invalid bid response - bidObject = bidfactory.createBid(2, bidRequest); - bidObject.bidderCode = bidRequest.bidder; - } - return bidObject; - } - - /** - * returns the top most accessible window - */ - function getTopMostWindow() { - var res = window; - - try { - while (top !== res) { - if (res.parent.location.href.length) { res = res.parent; } - } - } catch (e) {} - - return res; - } - - /* Create a function bound to a given object (assigning `this`, and arguments, - * optionally). Binding with arguments is also known as `curry`. - * Delegates to **ECMAScript 5**'s native `Function.bind` if available. - * We check for `func.bind` first, to fail fast when `func` is undefined. - * - * @param {function} func - * @param {optional} context - * @param {...any} var_args - * @return {function} - */ - var bind = function(func, context) { - return function() { - return func.apply(context, arguments); - }; - }; - - return Object.assign(this, new Adapter(STICKYADS_BIDDERCODE), { - callBids: _callBids, - formatBidObject: formatBidObject, - formatAdHTML: formatAdHTML, - getBiggerSize: getBiggerSize, - getBid: getBid, - getTopMostWindow: getTopMostWindow, - getComponentId: getComponentId, - getAPIName: getAPIName - }); -}; - -adaptermanager.registerBidAdapter(new StickyAdsTVAdapter(), 'stickyadstv'); -adaptermanager.aliasBidAdapter('stickyadstv', 'freewheel-ssp'); - -module.exports = StickyAdsTVAdapter; diff --git a/modules/tapsenseBidAdapter.js b/modules/tapsenseBidAdapter.js deleted file mode 100644 index a984f6cb8ab..00000000000 --- a/modules/tapsenseBidAdapter.js +++ /dev/null @@ -1,89 +0,0 @@ -// v0.0.1 - -const bidfactory = require('src/bidfactory.js'); -const bidmanager = require('src/bidmanager.js'); -const adloader = require('src/adloader'); -const utils = require('src/utils.js'); -const adaptermanager = require('src/adaptermanager'); - -const TapSenseAdapter = function TapSenseAdapter() { - const version = '0.0.1'; - const creativeSizes = [ - '320x50' - ]; - const validParams = [ - 'ufid', - 'refer', - 'ad_unit_id', // required - 'device_id', - 'lat', - 'long', - 'user', // required - 'price_floor', - 'test' - ]; - const SCRIPT_URL = 'https://ads04.tapsense.com/ads/headerad'; - let bids; - $$PREBID_GLOBAL$$.tapsense = {}; - function _callBids(params) { - bids = params.bids || []; - for (let i = 0; i < bids.length; i++) { - let bid = bids[i]; - let isValidSize = false; - if (!bid.sizes || !bid.params.user || !bid.params.ad_unit_id) { - return; - } - let parsedSizes = utils.parseSizesInput(bid.sizes); - for (let k = 0; k < parsedSizes.length; k++) { - if (creativeSizes.indexOf(parsedSizes[k]) > -1) { - isValidSize = true; - break; - } - } - if (isValidSize) { - let queryString = `?price=true&jsonp=1&callback=$$PREBID_GLOBAL$$.tapsense.callback_with_price_${bid.bidId}&version=${version}&`; - $$PREBID_GLOBAL$$.tapsense[`callback_with_price_${bid.bidId}`] = generateCallback(bid.bidId); - let keys = Object.keys(bid.params); - for (let j = 0; j < keys.length; j++) { - if (validParams.indexOf(keys[j]) < 0) continue; - queryString += encodeURIComponent(keys[j]) + '=' + encodeURIComponent(bid.params[keys[j]]) + '&'; - } - _requestBids(SCRIPT_URL + queryString); - } - } - } - - function generateCallback(bidId) { - return function tapsenseCallback(response, price) { - let bidObj; - if (response && price) { - let bidReq = utils.getBidRequest(bidId); - if (response.status.value === 'ok' && response.count_ad_units > 0) { - bidObj = bidfactory.createBid(1, bidObj); - bidObj.cpm = price; - bidObj.width = response.width; - bidObj.height = response.height; - bidObj.ad = response.ad_units[0].html; - } else { - bidObj = bidfactory.createBid(2, bidObj); - } - bidObj.bidderCode = bidReq.bidder; - bidmanager.addBidResponse(bidReq.placementCode, bidObj); - } else { - utils.logMessage('No prebid response'); - } - }; - } - - function _requestBids(scriptURL) { - adloader.loadScript(scriptURL); - } - - return { - callBids: _callBids - }; -}; - -adaptermanager.registerBidAdapter(new TapSenseAdapter(), 'tapsense'); - -module.exports = TapSenseAdapter; diff --git a/modules/thoughtleadrBidAdapter.js b/modules/thoughtleadrBidAdapter.js deleted file mode 100644 index 1202575a6cb..00000000000 --- a/modules/thoughtleadrBidAdapter.js +++ /dev/null @@ -1,191 +0,0 @@ -const bidfactory = require('src/bidfactory'); -const bidmanager = require('src/bidmanager'); -const utils = require('src/utils'); -const ajax_1 = require('src/ajax'); -const adaptermanager = require('src/adaptermanager'); - -const COOKIE_SYNC_ID = 'tldr-cookie-sync-div'; -const UID_KEY = 'tldr_uid'; -const URL_API = 'tldr' in window && tldr.config.root_url ? tldr.config.root_url : '//a.thoughtleadr.com/v4/'; -const URL_CDN = 'tldr' in window && tldr.config.cdn_url ? tldr.config.cdn_url : '//cdn.thoughtleadr.com/v4/'; -const BID_AVAILABLE = 1; -const BID_UNAVAILABLE = 2; - -function storageAvailable(type) { - try { - const storage = window[type]; - const x = '__storage_test__'; - storage.setItem(x, x); - storage.removeItem(x); - return true; - } catch (e) { - return e instanceof DOMException && ( - // everything except Firefox - e.code === 22 || - // Firefox - e.code === 1014 || - // test name field too, because code might not be present - // everything except Firefox - e.name === 'QuotaExceededError' || - // Firefox - e.name === 'NS_ERROR_DOM_QUOTA_REACHED') && - // acknowledge QuotaExceededError only if there's something already stored - storage.length !== 0; - } -} - -function getVal(key) { - if (storageAvailable('localStorage')) { - return localStorage[key]; - } - if (storageAvailable('sessionStorage')) { - return sessionStorage[key]; - } - return null; -} - -function setVal(key, val) { - if (storageAvailable('localStorage')) { - localStorage[key] = val; - } - if (storageAvailable('sessionStorage')) { - sessionStorage[key] = val; - } -} - -function getUid() { - let uid = getVal(UID_KEY); - if (!uid) { - uid = utils.generateUUID(null); - setVal(UID_KEY, uid); - } - return uid; -} - -function writeFriendlyFrame(html, container) { - const iframe = document.createElement('iframe'); - iframe.style.width = '0'; - iframe.style.height = '0'; - iframe.style.border = '0'; - - iframe.src = 'javascript:false'; - container.appendChild(iframe); - - const doc = iframe.contentWindow.document; - doc.body.innerHTML = html; - - const scripts = doc.body.getElementsByTagName('script'); - - for (let i = 0; i < scripts.length; i++) { - const scriptEl = scripts.item(i); - if (scriptEl.nodeName === 'SCRIPT') { - executeScript(scriptEl); - } - } - - return iframe; -} - -function executeScript(scriptEl) { - const newEl = document.createElement('script'); - newEl.innerText = scriptEl.text || scriptEl.textContent || scriptEl.innerHTML || ''; - - // ie-compatible copy-paste attributes - const attrs = scriptEl.attributes; - for (let i = attrs.length; i--;) { - newEl.setAttribute(attrs[i].name, attrs[i].value); - } - - if (scriptEl.parentNode) { - scriptEl.parentNode.replaceChild(newEl, scriptEl); - } -} - -const ThoughtleadrAdapter = (function () { - function ThoughtleadrAdapter() { - } - - ThoughtleadrAdapter.prototype.callBids = function (params) { - const bids = (params.bids || []).filter(function (bid) { - return ThoughtleadrAdapter.valid(bid); - }); - - for (let _i = 0, bids_1 = bids; _i < bids_1.length; _i++) { - const bid = bids_1[_i]; - this.requestPlacement(bid); - } - }; - - ThoughtleadrAdapter.prototype.requestPlacement = function (bid) { - const _this = this; - const uid = getUid(); - const size = ThoughtleadrAdapter.getSizes(bid.sizes); - - ajax_1.ajax('' + URL_API + bid.params.placementId + '/header-bid.json?uid=' + uid, function (response) { - const wonBid = JSON.parse(response); - if (wonBid.cookie_syncs) { - _this.syncCookies(wonBid.cookie_syncs); - } - - const script = document.createElement('script'); - script.src = URL_CDN + 'bid.js'; - script.setAttribute('header-bid-token', wonBid.header_bid_token); - - let bidObject; - if (wonBid && wonBid.amount) { - bidObject = bidfactory.createBid(BID_AVAILABLE); - bidObject.bidderCode = 'thoughtleadr'; - bidObject.cpm = wonBid.amount; - bidObject.ad = script.outerHTML; - bidObject.width = size.width; - bidObject.height = size.height; - } else { - bidObject = bidfactory.createBid(BID_UNAVAILABLE); - bidObject.bidderCode = 'thoughtleadr'; - } - bidmanager.addBidResponse(bid.placementCode, bidObject); - }, null); - }; - - ThoughtleadrAdapter.prototype.syncCookies = function (tags) { - if (!tags || !tags.length) { - return; - } - - let container = document.getElementById(COOKIE_SYNC_ID); - if (!container) { - container = document.createElement('div'); - container.id = COOKIE_SYNC_ID; - container.style.width = '0'; - container.style.height = '0'; - document.body.appendChild(container); - } - - for (let _i = 0, tags_1 = tags; _i < tags_1.length; _i++) { - const tag = tags_1[_i]; - writeFriendlyFrame(tag, container); - } - }; - - ThoughtleadrAdapter.valid = function (bid) { - return !!(bid && bid.params && typeof bid.params.placementId === 'string'); - }; - - ThoughtleadrAdapter.getSizes = function (sizes) { - const first = sizes[0]; - if (Array.isArray(first)) { - return ThoughtleadrAdapter.getSizes(first); - } - - return { - width: sizes[0], - height: sizes[1] - }; - }; - - return ThoughtleadrAdapter; -}()); - -adaptermanager.registerBidAdapter(new ThoughtleadrAdapter(), 'thoughtleadr'); - -module.exports = ThoughtleadrAdapter; diff --git a/modules/tremorBidAdapter.js b/modules/tremorBidAdapter.js deleted file mode 100644 index 1294c61e210..00000000000 --- a/modules/tremorBidAdapter.js +++ /dev/null @@ -1,167 +0,0 @@ -/* -* Tremor Video bid Adapter for prebid.js -* */ - -import Adapter from 'src/adapter'; -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; -import * as utils from 'src/utils'; -import {ajax} from 'src/ajax'; -import {STATUS} from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; - -const ENDPOINT = '.ads.tremorhub.com/ad/tag'; - -const OPTIONAL_PARAMS = [ - 'mediaId', 'mediaUrl', 'mediaTitle', 'contentLength', 'floor', - 'efloor', 'custom', 'categories', 'keywords', 'blockDomains', - 'c2', 'c3', 'c4', 'skip', 'skipmin', 'skipafter', 'delivery', - 'placement', 'videoMinBitrate', 'videoMaxBitrate' -]; - -/** - * Bidder adapter Tremor Video. Given the list of all ad unit tag IDs, - * sends out a bid request. When a bid response is back, registers the bid - * to Prebid.js. - * Steps: - * - Format and send the bid request - * - Evaluate and handle the response - * - Store potential VAST markup - * - Send request to ad server - * - intercept ad server response - * - Check if the vast wrapper URL is http://cdn.tremorhub.com/static/dummy.xml - * - If yes: then render the locally stored VAST markup by directly passing it to your player - * - Else: give the player the VAST wrapper from your ad server - */ -function TremorAdapter() { - let baseAdapter = new Adapter('tremor'); - - /* Prebid executes this function when the page asks to send out bid requests */ - baseAdapter.callBids = function (bidRequest) { - const bids = bidRequest.bids || []; - bids.filter(bid => valid(bid)) - .map(bid => { - let url = generateUrl(bid); - if (url) { - ajax(url, response => { - handleResponse(bid, response); - }, null, {method: 'GET', withCredentials: true}); - } - }); - }; - - /** - * Generates the url based on the parameters given. Sizes are required. - * The format is: [L,W] or [[L1,W1],...] - * @param bid - * @returns {string} - */ - function generateUrl(bid) { - // get the sizes - let width, height; - if (utils.isArray(bid.sizes) && bid.sizes.length === 2 && (!isNaN(bid.sizes[0]) && !isNaN(bid.sizes[1]))) { - width = bid.sizes[0]; - height = bid.sizes[1]; - } else if (typeof bid.sizes === 'object') { - // take the primary (first) size from the array - width = bid.sizes[0][0]; - height = bid.sizes[0][1]; - } - if (width && height) { - let scheme = ((document.location.protocol === 'https:') ? 'https' : 'http') + '://'; - let url = scheme + bid.params.supplyCode + ENDPOINT + '?adCode=' + bid.params.adCode; - - url += ('&playerWidth=' + width); - url += ('&playerHeight=' + height); - url += ('&srcPageUrl=' + encodeURIComponent(document.location.href)); - - OPTIONAL_PARAMS.forEach(param => { - if (bid.params[param]) { - url += ('&' + param + '=' + bid.params[param]); - } - }); - - url = (url + '&fmt=json'); - - return url; - } - } - - /* Notify Prebid of bid responses so bids can get in the auction */ - function handleResponse(bidReq, response) { - let bidResult; - - try { - bidResult = JSON.parse(response); - } catch (error) { - utils.logError(error); - } - - if (!bidResult || bidResult.error) { - let errorMessage = `in response for ${baseAdapter.getBidderCode()} adapter`; - if (bidResult && bidResult.error) { - errorMessage += `: ${bidResult.error}`; - } - utils.logError(errorMessage); - - // signal this response is complete - bidmanager.addBidResponse(bidReq.placementCode, createBid(STATUS.NO_BID)); - } - - if (bidResult.seatbid && bidResult.seatbid.length > 0) { - bidResult.seatbid[0].bid.forEach(tag => { - let status = STATUS.GOOD; - const bid = createBid(status, bidReq, tag); - bidmanager.addBidResponse(bidReq.placementCode, bid); - }); - } else { - // signal this response is complete with no bid - bidmanager.addBidResponse(bidReq.placementCode, createBid(STATUS.NO_BID)); - } - } - - /** - * We require the ad code and the supply code to generate a tag url - * @param bid - * @returns {*} - */ - function valid(bid) { - if (bid.params.adCode && bid.params.supplyCode) { - return bid; - } else { - utils.logError('missing bid params'); - } - } - - /** - * Create and return a bid object based on status and tag - * @param status - * @param reqBid - * @param response - */ - function createBid(status, reqBid, response) { - let bid = bidfactory.createBid(status, reqBid); - bid.code = baseAdapter.getBidderCode(); - bid.bidderCode = baseAdapter.getBidderCode(); - - if (response) { - bid.cpm = response.price; - bid.crid = response.crid; - bid.vastXml = response.adm; - bid.mediaType = 'video'; - } - - return bid; - } - - return Object.assign(this, { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode, - }); -} - -adaptermanager.registerBidAdapter(new TremorAdapter(), 'tremor', { - supportedMediaTypes: ['video'] -}); - -module.exports = TremorAdapter; diff --git a/modules/trionBidAdapter.js b/modules/trionBidAdapter.js index 642f8194b7f..c2488bd351a 100644 --- a/modules/trionBidAdapter.js +++ b/modules/trionBidAdapter.js @@ -1,129 +1,157 @@ -var CONSTANTS = require('src/constants.json'); -var utils = require('src/utils.js'); -var adloader = require('src/adloader.js'); -var bidmanager = require('src/bidmanager.js'); -var bidfactory = require('src/bidfactory.js'); -var Adapter = require('src/adapter.js').default; -var adaptermanager = require('src/adaptermanager'); - -const BID_REQUEST_BASE_URL = 'https://in-appadvertising.com/api/bidRequest?'; -const USER_SYNC_URL = 'https://in-appadvertising.com/api/userSync.js'; - -function TrionAdapter() { - var baseAdapter = new Adapter('trion'); - var userTag = null; - - baseAdapter.callBids = function (params) { - var bids = params.bids || []; - - if (!bids.length) { - return; +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BID_REQUEST_BASE_URL = 'https://in-appadvertising.com/api/bidRequest'; +const USER_SYNC_URL = 'https://in-appadvertising.com/api/userSync.html'; +const BIDDER_CODE = 'trion'; +const BASE_KEY = '_trion_'; + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function (bid) { + return !!(bid && bid.params && bid.params.pubId && bid.params.sectionId); + }, + buildRequests: function (validBidRequests) { + var bidRequests = []; + + for (var i = 0; i < validBidRequests.length; i++) { + var bid = validBidRequests[i]; + + var trionUrlParams = buildTrionUrlParams(bid); + + bidRequests.push({ + method: 'GET', + url: BID_REQUEST_BASE_URL, + bidRequest: bid, + data: trionUrlParams + }); } + return bidRequests; + }, - if (!window.TRION_INT) { - adloader.loadScript(USER_SYNC_URL, function () { - userTag = window.TRION_INT || {}; - userTag.pubId = utils.getBidIdParameter('pubId', bids[0].params); - userTag.sectionId = utils.getBidIdParameter('sectionId', bids[0].params); - if (!userTag.to) { - getBids(bids); - } else { - setTimeout(function () { - getBids(bids); - }, userTag.to); - } - }, true); - } else { - userTag = window.TRION_INT; - getBids(bids); - } - }; + interpretResponse: function (trionResponseObj, request) { + var bid = {}; + var bidResponses = []; + var bidRequest = request.bidRequest; + var responseBody = trionResponseObj ? trionResponseObj.body : {}; + + if (responseBody && responseBody.bidId && bidRequest) { + var result = responseBody.result; + + if (result && result.cpm && result.placeBid && result.ad) { + var cpm = parseInt(result.cpm, 10) / 100; - function getBids(bids) { - if (!userTag.int_t) { - userTag.int_t = window.TR_INT_T || -1; + bid.requestId = bidRequest.bidId; + bid.cpm = cpm; + bid.ad = result.ad; + bid.width = result.width; + bid.height = result.height; + bid.ttl = result.ttl; + bid.creativeId = result.creativeId; + bid.currency = result.currency; + bid.netRevenue = result.netRevenue; + bidResponses.push(bid); + } } - for (var i = 0; i < bids.length; i++) { - var bidRequest = bids[i]; - var bidId = bidRequest.bidId; - adloader.loadScript(buildTrionUrl(bidRequest, bidId)); + return bidResponses; + }, + getUserSyncs: function getUserSyncs(syncOptions) { + if (syncOptions.iframeEnabled) { + handlePostMessage(); + return [{ + type: 'iframe', + url: getSyncUrl() + }]; } } - function buildTrionUrl(bid, bidId) { - var pubId = utils.getBidIdParameter('pubId', bid.params); - var sectionId = utils.getBidIdParameter('sectionId', bid.params); - var re = utils.getBidIdParameter('re', bid.params); - var url = utils.getTopWindowUrl(); - var sizes = utils.parseSizesInput(bid.sizes).join(','); - - var trionUrl = BID_REQUEST_BASE_URL; - - trionUrl = utils.tryAppendQueryString(trionUrl, 'callback', '$$PREBID_GLOBAL$$.handleTrionCB'); - trionUrl = utils.tryAppendQueryString(trionUrl, 'bidId', bidId); - trionUrl = utils.tryAppendQueryString(trionUrl, 'pubId', pubId); - trionUrl = utils.tryAppendQueryString(trionUrl, 'sectionId', sectionId); - trionUrl = utils.tryAppendQueryString(trionUrl, 're', re); - trionUrl = utils.tryAppendQueryString(trionUrl, 'slot', bid.placementCode); - if (url) { - trionUrl += 'url=' + url + '&'; - } - if (sizes) { - trionUrl += 'sizes=' + sizes + '&'; - } - if (userTag) { - trionUrl += 'tag=' + encodeURIComponent(JSON.stringify(userTag)) + '&'; - } +}; +registerBidder(spec); - // remove the trailing "&" - if (trionUrl.lastIndexOf('&') === trionUrl.length - 1) { - trionUrl = trionUrl.substring(0, trionUrl.length - 1); - } +function getSyncUrl() { + var unParsedPubAndSection = getStorageData(BASE_KEY + 'lps') || ':'; + var pubSectionArray = unParsedPubAndSection.split(':') || []; + var pubId = pubSectionArray[0] || -1; + var sectionId = pubSectionArray[1] || -1; + var url = utils.getTopWindowUrl(); + return USER_SYNC_URL + `?p=${pubId}&s=${sectionId}&u=${url}`; +} - return trionUrl; +function buildTrionUrlParams(bid) { + var pubId = utils.getBidIdParameter('pubId', bid.params); + var sectionId = utils.getBidIdParameter('sectionId', bid.params); + var re = utils.getBidIdParameter('re', bid.params); + var url = utils.getTopWindowUrl(); + var sizes = utils.parseSizesInput(bid.sizes).join(','); + + var int_t = window.TR_INT_T && window.TR_INT_T != -1 ? window.TR_INT_T : null; + if (!int_t) { + int_t = getStorageData(BASE_KEY + 'int_t'); + } + if (int_t) { + setStorageData(BASE_KEY + 'int_t', int_t) + } + setStorageData(BASE_KEY + 'lps', pubId + ':' + sectionId); + var trionUrl = ''; + + trionUrl = utils.tryAppendQueryString(trionUrl, 'bidId', bid.bidId); + trionUrl = utils.tryAppendQueryString(trionUrl, 'pubId', pubId); + trionUrl = utils.tryAppendQueryString(trionUrl, 'sectionId', sectionId); + trionUrl = utils.tryAppendQueryString(trionUrl, 're', re); + if (url) { + trionUrl += 'url=' + url + '&'; + } + if (sizes) { + trionUrl += 'sizes=' + sizes + '&'; + } + if (int_t) { + trionUrl = utils.tryAppendQueryString(trionUrl, 'int_t', encodeURIComponent(int_t)); } - // expose the callback to the global object: - $$PREBID_GLOBAL$$.handleTrionCB = function (trionResponseObj) { - var bid; - var bidObj = {}; - var placementCode = ''; - - if (trionResponseObj && trionResponseObj.bidId) { - var bidCode; - var bidId = trionResponseObj.bidId; - var result = trionResponseObj && trionResponseObj.result; - - bidObj = utils.getBidRequest(bidId); - if (bidObj) { - bidCode = bidObj.bidder; - placementCode = bidObj.placementCode; - } + // remove the trailing "&" + if (trionUrl.lastIndexOf('&') === trionUrl.length - 1) { + trionUrl = trionUrl.substring(0, trionUrl.length - 1); + } + return trionUrl; +} - if (result && result.cpm && result.placeBid && result.ad) { - var cpm = parseInt(result.cpm, 10) / 100; - bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD, bidObj); - bid.bidderCode = bidCode; - bid.cpm = cpm; - bid.ad = result.ad; - bid.width = result.width; - bid.height = result.height; - } +function handlePostMessage() { + try { + if (window.addEventListener) { + window.addEventListener('message', acceptPostMessage); } - if (!bid) { - bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bidObj); + } catch (e) { + } +} + +export function getStorageData(key) { + var item = null; + try { + if (window.localStorage) { + item = window.localStorage.getItem(key); } - bidmanager.addBidResponse(placementCode, bid); - }; - - return Object.assign(this, { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode, - buildTrionUrl: buildTrionUrl - }); + } catch (e) { + } + return item; } -adaptermanager.registerBidAdapter(new TrionAdapter(), 'trion'); +export function setStorageData(key, item) { + try { + if (window.localStorage) { + window.localStorage.setItem(key, item); + } + } catch (e) { + } +} -module.exports = TrionAdapter; +export function acceptPostMessage(e) { + var message = e.data || ''; + if (message.indexOf(BASE_KEY + 'userId') !== 0) { + return; + } + var int_t = message.split(BASE_KEY + 'userId=')[1]; + if (int_t) { + setStorageData(BASE_KEY + 'int_t', int_t); + } +} diff --git a/modules/trionBidAdapter.md b/modules/trionBidAdapter.md new file mode 100644 index 00000000000..ac21a407144 --- /dev/null +++ b/modules/trionBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +**Module Name**: Trion Interactive Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: mgroh@trioninteractive.com +**Publisher Contact**: publishers@trioninteractive.com + +# Description + +This module connects to Trion's demand sources. It supports display, outstream, and rich media formats. +Trion will provide ``pubId`` and ``sectionId`` that are specific to your ad type. +Please reach out to ``publishers@trioninteractive.com`` to set up a trion account and above ids. +Use bidder code ```trion``` for all Trion traffic. + +# Test Parameters +``` + var adUnits = [ + { + code: 'ad-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: 'trion', + params: { + pubId: '12345', + sectionId: '1', + re : 'http://clicktrackingurl.com?re='// optional + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js deleted file mode 100644 index 141bdbf32cb..00000000000 --- a/modules/tripleliftBidAdapter.js +++ /dev/null @@ -1,149 +0,0 @@ -var utils = require('src/utils.js'); -var adloader = require('src/adloader.js'); -var bidmanager = require('src/bidmanager.js'); -var bidfactory = require('src/bidfactory.js'); -var adaptermanager = require('src/adaptermanager'); - -/* TripleLift bidder factory function -* Use to create a TripleLiftAdapter object -*/ - -var TripleLiftAdapter = function TripleLiftAdapter() { - var usersync = false; - - function _callBids(params) { - var tlReq = params.bids; - var bidsCount = tlReq.length; - - // set expected bids count for callback execution - // bidmanager.setExpectedBidsCount('triplelift',bidsCount); - - for (var i = 0; i < bidsCount; i++) { - var bidRequest = tlReq[i]; - var callbackId = bidRequest.bidId; - adloader.loadScript(buildTLCall(bidRequest, callbackId)); - // store a reference to the bidRequest from the callback id - // bidmanager.pbCallbackMap[callbackId] = bidRequest; - } - } - - function buildTLCall(bid, callbackId) { - // determine tag params - var inventoryCode = utils.getBidIdParameter('inventoryCode', bid.params); - var floor = utils.getBidIdParameter('floor', bid.params); - - // build our base tag, based on if we are http or https - var tlURI = '//tlx.3lift.com/header/auction?'; - var tlCall = document.location.protocol + tlURI; - - tlCall = utils.tryAppendQueryString(tlCall, 'callback', '$$PREBID_GLOBAL$$.TLCB'); - tlCall = utils.tryAppendQueryString(tlCall, 'lib', 'prebid'); - tlCall = utils.tryAppendQueryString(tlCall, 'v', '$prebid.version$'); - tlCall = utils.tryAppendQueryString(tlCall, 'callback_id', callbackId); - tlCall = utils.tryAppendQueryString(tlCall, 'inv_code', inventoryCode); - tlCall = utils.tryAppendQueryString(tlCall, 'floor', floor); - - // indicate whether flash support exists - tlCall = utils.tryAppendQueryString(tlCall, 'fe', isFlashEnabled()); - - // sizes takes a bit more logic - var sizeQueryString = utils.parseSizesInput(bid.sizes); - if (sizeQueryString) { - tlCall += 'size=' + sizeQueryString + '&'; - } - - // append referrer - var referrer = utils.getTopWindowUrl(); - tlCall = utils.tryAppendQueryString(tlCall, 'referrer', referrer); - - // remove the trailing "&" - if (tlCall.lastIndexOf('&') === tlCall.length - 1) { - tlCall = tlCall.substring(0, tlCall.length - 1); - } - - // @if NODE_ENV='debug' - utils.logMessage('tlCall request built: ' + tlCall); - // @endif - - // append a timer here to track latency - bid.startTime = new Date().getTime(); - - return tlCall; - } - - function isFlashEnabled() { - var hasFlash = 0; - try { - // check for Flash support in IE - var fo = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash'); - if (fo) { hasFlash = 1; } - } catch (e) { - if (navigator.mimeTypes && - navigator.mimeTypes['application/x-shockwave-flash'] !== undefined && - navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) { - hasFlash = 1; - } - } - return hasFlash; - } - - // expose the callback to the global object: - $$PREBID_GLOBAL$$.TLCB = function(tlResponseObj) { - if (tlResponseObj && tlResponseObj.callback_id) { - var bidObj = utils.getBidRequest(tlResponseObj.callback_id); - var placementCode = bidObj && bidObj.placementCode; - - // @if NODE_ENV='debug' - if (bidObj) { utils.logMessage('JSONP callback function called for inventory code: ' + bidObj.params.inventoryCode); } - // @endif - - var bid = []; - if (tlResponseObj && tlResponseObj.cpm && tlResponseObj.cpm !== 0) { - bid = bidfactory.createBid(1, bidObj); - bid.bidderCode = 'triplelift'; - bid.cpm = tlResponseObj.cpm; - bid.ad = tlResponseObj.ad; - bid.width = tlResponseObj.width; - bid.height = tlResponseObj.height; - bid.dealId = tlResponseObj.deal_id; - bidmanager.addBidResponse(placementCode, bid); - } else { - // no response data - // @if NODE_ENV='debug' - if (bidObj) { utils.logMessage('No prebid response from TripleLift for inventory code: ' + bidObj.params.inventoryCode); } - // @endif - bid = bidfactory.createBid(2, bidObj); - bid.bidderCode = 'triplelift'; - bidmanager.addBidResponse(placementCode, bid); - } - - // run usersyncs - if (!usersync) { - var iframe = utils.createInvisibleIframe(); - iframe.src = '//ib.3lift.com/sync'; - try { - document.body.appendChild(iframe); - } catch (error) { - utils.logError(error); - } - usersync = true; - // suppress TL ad tag from running additional usersyncs - window._tlSyncDone = true; - } - } else { - // no response data - // @if NODE_ENV='debug' - utils.logMessage('No prebid response for placement %%PLACEMENT%%'); - // @endif - } - }; - - return { - callBids: _callBids - - }; -}; - -adaptermanager.registerBidAdapter(new TripleLiftAdapter(), 'triplelift'); - -module.exports = TripleLiftAdapter; diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index 13f893a841d..ec1f0247455 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -1,165 +1,151 @@ -const utils = require('src/utils.js'); -const bidfactory = require('src/bidfactory.js'); -const bidmanager = require('src/bidmanager.js'); -const adloader = require('src/adloader'); -const adaptermanager = require('src/adaptermanager'); -const CONSTANTS = require('src/constants.json'); - -var TrustxAdapter = function TrustxAdapter() { - const bidderCode = 'trustx'; - const reqHost = '//sofia.trustx.org'; - const reqPath = '/hb?'; - const LOG_ERROR_MESS = { - noAuid: 'Bid from response has no auid parameter - ', - noAdm: 'Bid from response has no adm parameter - ', - noBid: 'Array of bid objects is empty', - noPlacementCode: 'Can\'t find placementCode for bid with auid - ', - havePCodeFor: ', placementCode is available only for the following uids - ', - emptyUids: 'Uids should be not empty', - emptySeatbid: 'Seatbid array from response has empty item', - emptyResponse: 'Response is empty', - hasEmptySeatbidArray: 'Response has empty seatbid array', - hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' - }; +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'trustx'; +const ENDPOINT_URL = '//sofia.trustx.org/hb'; +const TIME_TO_LIVE = 360; +const ADAPTER_SYNC_URL = '//sofia.trustx.org/push_sync'; +const LOG_ERROR_MESS = { + noAuid: 'Bid from response has no auid parameter - ', + noAdm: 'Bid from response has no adm parameter - ', + noBid: 'Array of bid objects is empty', + noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', + emptyUids: 'Uids should be not empty', + emptySeatbid: 'Seatbid array from response has empty item', + emptyResponse: 'Response is empty', + hasEmptySeatbidArray: 'Response has empty seatbid array', + hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' +}; +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.uid; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests) { + const auids = []; + const bidsMap = {}; + const bids = validBidRequests || []; + let priceType = 'net'; + let reqId; + + bids.forEach(bid => { + if (bid.params.priceType === 'gross') { + priceType = 'gross'; + } + reqId = bid.bidderRequestId; + if (!bidsMap[bid.params.uid]) { + bidsMap[bid.params.uid] = [bid]; + auids.push(bid.params.uid); + } else { + bidsMap[bid.params.uid].push(bid); + } + }); - function _makeHandler(auids, placementMap) { - var cbName = bidderCode + '_callback_wrapper_' + auids.join('_'); - $$PREBID_GLOBAL$$[cbName] = function(resp) { - delete $$PREBID_GLOBAL$$[cbName]; - _responseProcessing(resp, auids, placementMap); + const payload = { + u: utils.getTopWindowUrl(), + pt: priceType, + auids: auids.join(','), + r: reqId }; - return '$$PREBID_GLOBAL$$.' + cbName; - } - function _sendRequest(auids, placementMap) { - var query = []; - var path = reqPath; - query.push('u=' + encodeURIComponent(location.href)); - query.push('auids=' + encodeURIComponent(auids.join(','))); - query.push('cb=' + _makeHandler(auids, placementMap)); - query.push('pt=' + (window.globalPrebidTrustxPriceType === 'gross' ? 'gross' : 'net')); - - adloader.loadScript(reqHost + path + query.join('&')); - } - - function _callBids(params) { - var auids = []; - var placementMap = {}; - var hasBid; - var bid; - var bids = params.bids || []; - for (var i = 0; i < bids.length; i++) { - bid = bids[i]; - if (bid && bid.bidder === bidderCode && bid.placementCode) { - hasBid = true; - if (bid.params && bid.params.uid) { - if (!placementMap[bid.params.uid]) { - placementMap[bid.params.uid] = [bid.placementCode]; - auids.push(bid.params.uid); - } else { - placementMap[bid.params.uid].push(bid.placementCode); - } - } - } + return { + method: 'GET', + url: ENDPOINT_URL, + data: payload, + bidsMap: bidsMap, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {*} bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + serverResponse = serverResponse && serverResponse.body + const bidResponses = []; + const bidsMap = bidRequest.bidsMap; + const priceType = bidRequest.data.pt; + + let errorMessage; + + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; } - if (auids.length) { - _sendRequest(auids, placementMap); - } else if (hasBid) { - utils.logError(LOG_ERROR_MESS.emptyUids); + if (!errorMessage && serverResponse.seatbid) { + serverResponse.seatbid.forEach(respItem => { + _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses); + }); } - } - - function _getBidFromResponse(resp) { - if (!resp) { - utils.logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!resp.bid) { - utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(resp)); - } else if (!resp.bid[0]) { - utils.logError(LOG_ERROR_MESS.noBid); + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + }, + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: ADAPTER_SYNC_URL + }]; } - return resp && resp.bid && resp.bid[0]; } - - function _forEachPlacement(error, bid, placementCode) { - var bidObject; - if (error) { - bidObject = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, bid); - } else { - bidObject = bidfactory.createBid(CONSTANTS.STATUS.GOOD, bid); - bidObject.cpm = bid.price; - bidObject.ad = bid.adm; - bidObject.width = bid.w; - bidObject.height = bid.h; - if (bid.dealid) { - bidObject.dealId = bid.dealid; - } - } - bidObject.bidderCode = bidderCode; - bidmanager.addBidResponse(placementCode, bidObject); +} + +function _getBidFromResponse(respItem) { + if (!respItem) { + utils.logError(LOG_ERROR_MESS.emptySeatbid); + } else if (!respItem.bid) { + utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + } else if (!respItem.bid[0]) { + utils.logError(LOG_ERROR_MESS.noBid); } - - function _addBidResponse(bid, auids, placementMap) { - if (!bid) return; - var errorMessage, placementCodes; - if (!bid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(bid); - else { - placementCodes = placementMap.hasOwnProperty(bid.auid) && placementMap[bid.auid]; - if (!placementCodes) { - errorMessage = LOG_ERROR_MESS.noPlacementCode + bid.auid + LOG_ERROR_MESS.havePCodeFor + auids.join(','); - } - } - - if (!errorMessage) { - if (!bid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(bid); - - var l = placementCodes.length; - while (l--) { - _forEachPlacement(errorMessage, bid, placementCodes[l]); - } - - delete placementMap[bid.auid]; - } - - if (errorMessage) { - utils.logError(errorMessage); + return respItem && respItem.bid && respItem.bid[0]; +} + +function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { + if (!serverBid) return; + let errorMessage; + if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); + if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + awaitingBids.forEach(bid => { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + bidderCode: spec.code, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: priceType !== 'gross', + ttl: TIME_TO_LIVE, + ad: serverBid.adm, + dealId: serverBid.dealid + }; + bidResponses.push(bidResponse); + }); + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; } } - - function _responseProcessing(resp, auids, placementMap) { - var errorMessage; - - if (!resp) errorMessage = LOG_ERROR_MESS.emptyResponse; - else if (resp.seatbid && !resp.seatbid.length) errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; - - if (!errorMessage) { - resp = resp.seatbid || []; - var l = resp.length; - while (l--) { - _addBidResponse(_getBidFromResponse(resp[l]), auids, placementMap); - } - } - - var n, bidObj; - for (var auid in placementMap) { - if (placementMap.hasOwnProperty(auid) && placementMap[auid]) { - n = placementMap[auid].length; - while (n--) { - bidObj = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); - bidObj.bidderCode = bidderCode; - bidmanager.addBidResponse(placementMap[auid][n], bidObj); - } - } - } - - if (errorMessage) utils.logError(errorMessage); + if (errorMessage) { + utils.logError(errorMessage); } +} - return { - callBids: _callBids - }; -}; - -adaptermanager.registerBidAdapter(new TrustxAdapter(), 'trustx'); - -module.exports = TrustxAdapter; +registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md new file mode 100755 index 00000000000..ca407b0c5e8 --- /dev/null +++ b/modules/trustxBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: TrustX Bidder Adapter +Module Type: Bidder Adapter +Maintainer: paul@trustx.org + +# Description + +Module that connects to TrustX demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "trustx", + params: { + uid: '44', + priceType: 'gross' // by default is 'net' + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "trustx", + params: { + uid: 45, + priceType: 'gross' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/twengaBidAdapter.js b/modules/twengaBidAdapter.js deleted file mode 100644 index 3a0e1016937..00000000000 --- a/modules/twengaBidAdapter.js +++ /dev/null @@ -1,136 +0,0 @@ -var CONSTANTS = require('src/constants.json'); -var utils = require('src/utils.js'); -var adloader = require('src/adloader.js'); -var bidmanager = require('src/bidmanager.js'); -var bidfactory = require('src/bidfactory.js'); -var Adapter = require('src/adapter.js').default; -var adaptermanager = require('src/adaptermanager'); - -function TwengaAdapter() { - var baseAdapter = new Adapter('twenga'); - - baseAdapter.callBids = function (params) { - for (var i = 0; i < params.bids.length; i++) { - var bidRequest = params.bids[i]; - var callbackId = bidRequest.bidId; - adloader.loadScript(buildBidCall(bidRequest, callbackId)); - } - }; - - function buildBidCall(bid, callbackId) { - var bidUrl = '//rtb.t.c4tw.net/Bid?'; - bidUrl = utils.tryAppendQueryString(bidUrl, 's', 'h'); - bidUrl = utils.tryAppendQueryString(bidUrl, 'callback', '$$PREBID_GLOBAL$$.handleTwCB'); - bidUrl = utils.tryAppendQueryString(bidUrl, 'callback_uid', callbackId); - bidUrl = utils.tryAppendQueryString(bidUrl, 'referrer', utils.getTopWindowUrl()); - if (bid.params) { - for (var key in bid.params) { - var value = bid.params[key]; - switch (key) { - case 'placementId': key = 'id'; break; - case 'siteId': key = 'sid'; break; - case 'publisherId': key = 'pid'; break; - case 'currency': key = 'cur'; break; - case 'bidFloor': key = 'min'; break; - case 'country': key = 'gz'; break; - } - bidUrl = utils.tryAppendQueryString(bidUrl, key, value); - } - } - - var sizes = utils.parseSizesInput(bid.sizes); - if (sizes.length > 0) { - bidUrl = utils.tryAppendQueryString(bidUrl, 'size', sizes.join(',')); - } - - bidUrl += 'ta=1'; - - // @if NODE_ENV='debug' - utils.logMessage('bid request built: ' + bidUrl); - - // @endif - - // append a timer here to track latency - bid.startTime = new Date().getTime(); - - return bidUrl; - } - - // expose the callback to the global object: - $$PREBID_GLOBAL$$.handleTwCB = function (bidResponseObj) { - var bidCode; - - if (bidResponseObj && bidResponseObj.callback_uid) { - var responseCPM; - var id = bidResponseObj.callback_uid; - var placementCode = ''; - var bidObj = utils.getBidRequest(id); - if (bidObj) { - bidCode = bidObj.bidder; - - placementCode = bidObj.placementCode; - - bidObj.status = CONSTANTS.STATUS.GOOD; - } - - // @if NODE_ENV='debug' - utils.logMessage('JSONP callback function called for ad ID: ' + id); - - // @endif - var bid = []; - if (bidResponseObj.result && - bidResponseObj.result.cpm && - bidResponseObj.result.cpm !== 0 && - bidResponseObj.result.ad) { - var result = bidResponseObj.result; - - responseCPM = parseInt(result.cpm, 10); - - // CPM response from /Bid is dollar/cent multiplied by 10000 - // in order to avoid using floats - // switch CPM to "dollar/cent" - responseCPM = responseCPM / 10000; - - var ad = result.ad.replace('%%WP%%', result.cpm); - - // store bid response - // bid status is good (indicating 1) - bid = bidfactory.createBid(1, bidObj); - bid.creative_id = result.creative_id; - bid.bidderCode = bidCode; - bid.cpm = responseCPM; - if (ad && (ad.lastIndexOf('http', 0) === 0 || ad.lastIndexOf('//', 0) === 0)) { bid.adUrl = ad; } else { bid.ad = ad; } - bid.width = result.width; - bid.height = result.height; - - bidmanager.addBidResponse(placementCode, bid); - } else { - // no response data - // @if NODE_ENV='debug' - utils.logMessage('No prebid response from Twenga for placement code ' + placementCode); - - // @endif - // indicate that there is no bid for this placement - bid = bidfactory.createBid(2, bidObj); - bid.bidderCode = bidCode; - bidmanager.addBidResponse(placementCode, bid); - } - } else { - // no response data - // @if NODE_ENV='debug' - utils.logMessage('No prebid response for placement %%PLACEMENT%%'); - - // @endif - } - }; - - return Object.assign(this, { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode, - buildBidCall: buildBidCall - }); -} - -adaptermanager.registerBidAdapter(new TwengaAdapter(), 'twenga'); - -module.exports = TwengaAdapter; diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 978c7508002..7f652e38c3d 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -1,95 +1,95 @@ -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; -import {ajax} from 'src/ajax'; -import {STATUS} from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; -const VER = 'ADGENT_PREBID-2017051801'; +const VER = 'ADGENT_PREBID-2018011501'; +const BID_REQUEST_BASE_URL = '//hb.aralego.com/header'; const UCFUNNEL_BIDDER_CODE = 'ucfunnel'; -function UcfunnelAdapter() { - function _callBids(params) { - let bids = params.bids || []; +export const spec = { + code: UCFUNNEL_BIDDER_CODE, + supportedMediaTypes: [BANNER], + /** + * Check if the bid is a valid zone ID in either number or string form + * @param {object} bid the ucfunnel bid to validate + * @return boolean for whether or not a bid is valid + */ + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.adid && typeof bid.params.adid === 'string'); + }, - bids.forEach((bid) => { - try { - ajax(buildOptimizedCall(bid), bidCallback, undefined, { withCredentials: true }); - } catch (err) { - utils.logError('Error sending ucfunnel request for placement code ' + bid.placementCode, null, err); - } + /** + * Format the bid request object for our endpoint + * @param {BidRequest[]} bidRequests Array of ucfunnel bidders + * @return object of parameters for Prebid AJAX request + */ + buildRequests: function(validBidRequests) { + var bidRequests = []; + for (var i = 0; i < validBidRequests.length; i++) { + var bid = validBidRequests[i]; - function bidCallback(responseText) { - try { - utils.logMessage('XHR callback function called for placement code: ' + bid.placementCode); - handleRpCB(responseText, bid); - } catch (err) { - if (typeof err === 'string') { - utils.logWarn(`${err} when processing ucfunnel response for placement code ${bid.placementCode}`); - } else { - utils.logError('Error processing ucfunnel response for placement code ' + bid.placementCode, null, err); - } + var ucfunnelUrlParams = buildUrlParams(bid); - // indicate that there is no bid for this placement - let badBid = bidfactory.createBid(STATUS.NO_BID, bid); - badBid.bidderCode = bid.bidder; - badBid.error = err; - bidmanager.addBidResponse(bid.placementCode, badBid); - } - } - }); - } - - function buildOptimizedCall(bid) { - bid.startTime = new Date().getTime(); - - const host = utils.getTopWindowLocation().host; - const page = utils.getTopWindowLocation().pathname; - const refer = document.referrer; - const language = navigator.language; - const dnt = (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0; - - let queryString = [ - 'ifr', 0, - 'bl', language, - 'je', 1, - 'dnt', dnt, - 'host', host, - 'u', page, - 'ru', refer, - 'adid', bid.params.adid, - 'ver', VER - ]; + bidRequests.push({ + method: 'GET', + url: BID_REQUEST_BASE_URL, + bidRequest: bid, + data: ucfunnelUrlParams + }); + } + return bidRequests; + }, - return queryString.reduce( - (memo, curr, index) => - index % 2 === 0 && queryString[index + 1] !== undefined - ? memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' - : memo, - '//agent.aralego.com/header?' - ).slice(0, -1); - } - - function handleRpCB(responseText, bidRequest) { - let ad = JSON.parse(responseText); // can throw + /** + * Format ucfunnel responses as Prebid bid responses + * @param {ucfunnelResponseObj} ucfunnelResponse A successful response from ucfunnel. + * @return {Bid[]} An array of formatted bids. + */ + interpretResponse: function (ucfunnelResponseObj, request) { + var bidResponses = []; + var bidRequest = request.bidRequest; + var responseBody = ucfunnelResponseObj ? ucfunnelResponseObj.body : {}; - let bid = bidfactory.createBid(STATUS.GOOD, bidRequest); - bid.creative_id = ad.ad_id; - bid.bidderCode = UCFUNNEL_BIDDER_CODE; - bid.cpm = ad.cpm || 0; - bid.ad = ad.adm; - bid.width = ad.width; - bid.height = ad.height; - bid.dealId = ad.deal; + bidResponses.push({ + requestId: bidRequest.bidId, + cpm: responseBody.cpm || 0, + width: responseBody.width, + height: responseBody.height, + creativeId: responseBody.ad_id, + dealId: responseBody.deal || null, + currency: 'USD', + netRevenue: true, + ttl: 1000, + mediaType: BANNER, + ad: responseBody.adm + }); - bidmanager.addBidResponse(bidRequest.placementCode, bid); + return bidResponses; } - - return { - callBids: _callBids - }; }; +registerBidder(spec); + +function buildUrlParams(bid) { + const host = utils.getTopWindowLocation().host; + const page = utils.getTopWindowLocation().pathname; + const refer = document.referrer; + const language = navigator.language; + const dnt = (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0; -adaptermanager.registerBidAdapter(new UcfunnelAdapter(), UCFUNNEL_BIDDER_CODE); + let queryString = [ + 'ifr', '0', + 'bl', language, + 'je', '1', + 'dnt', dnt, + 'host', host, + 'u', page, + 'ru', refer, + 'adid', utils.getBidIdParameter('adid', bid.params), + 'ver', VER + ]; -module.exports = UcfunnelAdapter; + return queryString.reduce( + (memo, curr, index) => + index % 2 === 0 && queryString[index + 1] !== undefined ? memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' : memo, '' + ).slice(0, -1); +} diff --git a/modules/ucfunnelBidAdapter.md b/modules/ucfunnelBidAdapter.md new file mode 100644 index 00000000000..717d2a0089c --- /dev/null +++ b/modules/ucfunnelBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: ucfunnel Bid Adapter +Module Type: Bidder Adapter +Maintainer: ryan.chou@ucfunnel.com +``` + +# Description + +This module connects to ucfunnel's demand sources. It supports display, and rich media formats. +ucfunnel will provide ``adid`` that are specific to your ad type. +Please reach out to ``pr@ucfunnel.com`` to set up an ucfunnel account and above ids. +Use bidder code ```ucfunnel``` for all ucfunnel traffic. + +# Test Parameters + +``` + var adUnits = [ + { + code: 'test-LERC', + sizes: [[300, 250]], + bids: [{ + bidder: 'ucfunnel', + params: { + adid: "test-ad-83444226E44368D1E32E49EEBE6D29" //String - required + } + } + ]; +``` \ No newline at end of file diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index 3c9e28e9658..0b2009d8133 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -1,112 +1,117 @@ +import * as utils from 'src/utils'; import { config } from 'src/config'; -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader.js'); -var utils = require('src/utils.js'); -var adaptermanager = require('src/adaptermanager'); - -function UnderdogMediaAdapter() { - const UDM_ADAPTER_VERSION = '1.0.0'; - var getJsStaticUrl = window.location.protocol + '//udmserve.net/udm/img.fetch?tid=1;dt=9;callback=$$PREBID_GLOBAL$$.handleUnderdogMediaCB;'; - var bidParams = {}; - - function _callBids(params) { - bidParams = params; +import { registerBidder } from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'underdogmedia'; +const UDM_ADAPTER_VERSION = '1.0'; + +export const spec = { + code: BIDDER_CODE, + bidParams: [], + + isBidRequestValid: function (bid) { + return !!((bid.params && bid.params.siteId) && (bid.sizes && bid.sizes.length > 0)); + }, + + buildRequests: function (validBidRequests) { var sizes = []; var siteId = 0; - bidParams.bids.forEach(bidParam => { + validBidRequests.forEach(bidParam => { sizes = utils.flatten(sizes, utils.parseSizesInput(bidParam.sizes)); siteId = bidParam.params.siteId; }); - adloader.loadScript(getJsStaticUrl + 'sid=' + siteId + ';sizes=' + sizes.join(','), null, false); - } - function _callback(response) { - var mids = response.mids; - bidParams.bids.forEach(bidParam => { - var filled = false; - mids.forEach(mid => { + return { + method: 'GET', + url: `${window.location.protocol}//udmserve.net/udm/img.fetch`, + data: `tid=1;dt=10;sid=${siteId};sizes=${sizes.join(',')}`, + bidParams: validBidRequests + }; + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + bidRequest.bidParams.forEach(bidParam => { + serverResponse.body.mids.forEach(mid => { if (mid.useCount > 0) { return; } + if (!mid.useCount) { mid.useCount = 0; } + var size_not_found = true; utils.parseSizesInput(bidParam.sizes).forEach(size => { if (size === mid.width + 'x' + mid.height) { size_not_found = false; } }); + if (size_not_found) { return; } - var bid = bidfactory.createBid(1, bidParam); - bid.bidderCode = bidParam.bidder; - bid.width = mid.width; - bid.height = mid.height; + const bidResponse = { + requestId: bidParam.bidId, + bidderCode: spec.code, + cpm: parseFloat(mid.cpm), + width: mid.width, + height: mid.height, + ad: mid.ad_code_html, + creativeId: mid.mid, + currency: 'USD', + netRevenue: false, + ttl: config.getConfig('_bidderTimeout'), + }; - bid.cpm = parseFloat(mid.cpm); - if (bid.cpm <= 0) { + if (bidResponse.cpm <= 0) { return; } - mid.useCount++; - bid.ad = mid.ad_code_html; - bid.ad = _makeNotification(bid, mid, bidParam) + bid.ad; - if (!(bid.ad || bid.adUrl)) { + if (bidResponse.ad.length <= 0) { return; } - bidmanager.addBidResponse(bidParam.placementCode, bid); - filled = true; + + mid.useCount++; + + bidResponse.ad += makeNotification(bidResponse, mid, bidParam); + + bidResponses.push(bidResponse); }); - if (!filled) { - var nobid = bidfactory.createBid(2, bidParam); - nobid.bidderCode = bidParam.bidder; - bidmanager.addBidResponse(bidParam.placementCode, nobid); - } }); - } - $$PREBID_GLOBAL$$.handleUnderdogMediaCB = _callback; + return bidResponses; + }, +}; - function _makeNotification(bid, mid, bidParam) { - var url = mid.notification_url; +function makeNotification (bid, mid, bidParam) { + var url = mid.notification_url; - url += UDM_ADAPTER_VERSION; - url += ';cb=' + Math.random(); - url += ';qqq=' + (1 / bid.cpm); - url += ';hbt=' + config.getConfig('bidderTimeout'); - url += ';style=adapter'; - url += ';vis=' + encodeURIComponent(document.visibilityState); + url += UDM_ADAPTER_VERSION; + url += ';cb=' + Math.random(); + url += ';qqq=' + (1 / bid.cpm); + url += ';hbt=' + config.getConfig('_bidderTimeout'); + url += ';style=adapter'; + url += ';vis=' + encodeURIComponent(document.visibilityState); - url += ';traffic_info=' + encodeURIComponent(JSON.stringify(_getUrlVars())); - if (bidParam.params.subId) { - url += ';subid=' + encodeURIComponent(bidParam.params.subId); - } - return ''; + url += ';traffic_info=' + encodeURIComponent(JSON.stringify(getUrlVars())); + if (bidParam.params.subId) { + url += ';subid=' + encodeURIComponent(bidParam.params.subId); } + return ''; +} - function _getUrlVars() { - var vars = {}; - var hash; - var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); - for (var i = 0; i < hashes.length; i++) { - hash = hashes[i].split('='); - if (!hash[0].match(/^utm/)) { - continue; - } +function getUrlVars() { + var vars = {}; + var hash; + var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); + for (var i = 0; i < hashes.length; i++) { + hash = hashes[i].split('='); + if (hash[0].match(/^utm_/)) { vars[hash[0]] = hash[1].substr(0, 150); } - return vars; } - - return { - callBids: _callBids - }; + return vars; } -adaptermanager.registerBidAdapter(new UnderdogMediaAdapter(), 'underdogmedia'); - -module.exports = UnderdogMediaAdapter; +registerBidder(spec); diff --git a/modules/underdogmediaBidAdapter.md b/modules/underdogmediaBidAdapter.md new file mode 100644 index 00000000000..f652e2fcbbf --- /dev/null +++ b/modules/underdogmediaBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +**Module Name**: Underdog Media Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: jake@underdogmedia.com + +# Description + +Module that connects to Underdog Media's servers to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "underdogmedia", + params: { + siteId: '12143' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js new file mode 100644 index 00000000000..ae9abee8e88 --- /dev/null +++ b/modules/undertoneBidAdapter.js @@ -0,0 +1,79 @@ +/** + * Adapter to send bids to Undertone + */ + +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'undertone'; +const URL = '//hb.undertone.com/hb'; + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + if (bid && bid.params && bid.params.publisherId && bid.params.placementId) { + bid.params.publisherId = parseInt(bid.params.publisherId); + return true; + } + }, + buildRequests: function(validBidRequests) { + const payload = { + 'x-ut-hb-params': [] + }; + const location = utils.getTopWindowLocation(); + let domain = /[-\w]+\.(?:[-\w]+\.xn--[-\w]+|[-\w]{3,}|[-\w]+\.[-\w]{2})$/i.exec(location.host); + if (domain == null || domain.length == 0) { + domain = null; + } else { + domain = domain[0]; + } + + const pubid = validBidRequests[0].params.publisherId; + const REQ_URL = `${URL}?pid=${pubid}&domain=${domain}`; + + validBidRequests.map(bidReq => { + const bid = { + bidRequestId: bidReq.bidId, + hbadaptor: 'prebid', + url: location.href, + domain: domain, + placementId: bidReq.params.placementId, + publisherId: bidReq.params.publisherId, + sizes: bidReq.sizes, + params: bidReq.params + }; + payload['x-ut-hb-params'].push(bid); + }); + return { + method: 'POST', + url: REQ_URL, + withCredentials: true, + data: JSON.stringify(payload) + }; + }, + interpretResponse: function(serverResponse, request) { + const bids = []; + const body = serverResponse.body; + + if (body && Array.isArray(body) && body.length > 0) { + body.forEach((bidRes) => { + if (bidRes.ad && bidRes.cpm > 0) { + const bid = { + requestId: bidRes.bidRequestId, + cpm: bidRes.cpm, + width: bidRes.width, + height: bidRes.height, + creativeId: bidRes.adId, + currency: bidRes.currency, + netRevenue: bidRes.netRevenue, + ttl: bidRes.ttl, + ad: bidRes.ad + }; + bids.push(bid); + } + }); + } + return bids; + } +}; +registerBidder(spec); diff --git a/modules/undertoneBidAdapter.md b/modules/undertoneBidAdapter.md new file mode 100644 index 00000000000..8ac84b77bd8 --- /dev/null +++ b/modules/undertoneBidAdapter.md @@ -0,0 +1,29 @@ +# Overview + +``` +Module Name: Example Bidder Adapter +Module Type: Bidder Adapter +Maintainer: RampProgrammatic@perion.com +``` +# Description + +Module that connects to Undertone's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "undertone", + params: { + placementId: '10433394', + publisherId: 12345 + } + } + ] + } + ]; +``` diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index 0f6b6e40901..94fa716799a 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -1,119 +1,101 @@ -import { ajax } from 'src/ajax' -import bidfactory from 'src/bidfactory' -import bidmanager from 'src/bidmanager' import * as utils from 'src/utils' -import { STATUS } from 'src/constants' import { Renderer } from 'src/Renderer' -import adaptermanager from 'src/adaptermanager' - -function createRenderHandler({ bidResponseBid, rendererConfig }) { - function createApi() { - parent.window.unruly['native'].prebid = parent.window.unruly['native'].prebid || {} - parent.window.unruly['native'].prebid.uq = parent.window.unruly['native'].prebid.uq || [] +import { registerBidder } from 'src/adapters/bidderFactory' +import { VIDEO } from 'src/mediaTypes' + +function configureUniversalTag (exchangeRenderer) { + parent.window.unruly = parent.window.unruly || {}; + parent.window.unruly['native'] = parent.window.unruly['native'] || {}; + parent.window.unruly['native'].siteId = parent.window.unruly['native'].siteId || exchangeRenderer.siteId; + parent.window.unruly['native'].supplyMode = 'prebid'; +} - return { - render(bidResponseBid) { - parent.window.unruly['native'].prebid.uq.push(['render', bidResponseBid]) - }, - onLoaded(bidResponseBid) {} - } - } +function configureRendererQueue () { + parent.window.unruly['native'].prebid = parent.window.unruly['native'].prebid || {}; + parent.window.unruly['native'].prebid.uq = parent.window.unruly['native'].prebid.uq || []; +} - parent.window.unruly = parent.window.unruly || {} - parent.window.unruly['native'] = parent.window.unruly['native'] || {} - parent.window.unruly['native'].siteId = parent.window.unruly['native'].siteId || rendererConfig.siteId - - const api = createApi() - return { - render() { - api.render(bidResponseBid) - }, - onRendererLoad() { - api.onLoaded(bidResponseBid) - } - } +function notifyRenderer (bidResponseBid) { + parent.window.unruly['native'].prebid.uq.push(['render', bidResponseBid]); } -function createBidResponseHandler(bidRequestBids) { - return { - onBidResponse(responseBody) { - try { - const exchangeResponse = JSON.parse(responseBody) - exchangeResponse.bids.forEach((exchangeBid) => { - const bidResponseBid = bidfactory.createBid(exchangeBid.ext.statusCode, exchangeBid) - - Object.assign( - bidResponseBid, - exchangeBid - ) - - if (exchangeBid.ext.renderer) { - const rendererParams = exchangeBid.ext.renderer - const renderHandler = createRenderHandler({ - bidResponseBid, - rendererConfig: rendererParams.config - }) - - bidResponseBid.renderer = Renderer.install( - Object.assign( - {}, - rendererParams, - { callback: () => renderHandler.onRendererLoad() } - ) - ) - bidResponseBid.renderer.setRender(() => renderHandler.render()) +const serverResponseToBid = (bid, rendererInstance) => ({ + requestId: bid.bidId, + cpm: bid.cpm, + width: bid.width, + height: bid.height, + vastUrl: bid.vastUrl, + netRevenue: true, + creativeId: bid.bidId, + ttl: 360, + currency: 'USD', + renderer: rendererInstance +}); + +const buildPrebidResponseAndInstallRenderer = bids => + bids + .filter(serverBid => !!utils.deepAccess(serverBid, 'ext.renderer')) + .map(serverBid => { + const exchangeRenderer = utils.deepAccess(serverBid, 'ext.renderer'); + configureUniversalTag(exchangeRenderer); + configureRendererQueue(); + + const rendererInstance = Renderer.install(Object.assign({}, exchangeRenderer, { callback: () => {} })); + return { rendererInstance, serverBid }; + }) + .map( + ({rendererInstance, serverBid}) => { + const prebidBid = serverResponseToBid(serverBid, rendererInstance); + + const rendererConfig = Object.assign( + {}, + prebidBid, + { + renderer: rendererInstance, + adUnitCode: serverBid.ext.adUnitCode } + ); - bidmanager.addBidResponse(exchangeBid.ext.placementCode, bidResponseBid) - }) - } catch (error) { - utils.logError(error); - bidRequestBids.forEach(bidRequestBid => { - const bidResponseBid = bidfactory.createBid(STATUS.NO_BID) - bidmanager.addBidResponse(bidRequestBid.placementCode, bidResponseBid) - }) - } - } - } -} + rendererInstance.setRender(() => { notifyRenderer(rendererConfig) }); -function UnrulyAdapter() { - const adapter = { - exchangeUrl: 'https://targeting.unrulymedia.com/prebid', - callBids({ bids: bidRequestBids }) { - if (!bidRequestBids || bidRequestBids.length === 0) { - return + return prebidBid; } + ); - const videoMediaType = utils.deepAccess(bidRequestBids[0], 'mediaTypes.video') - const context = utils.deepAccess(bidRequestBids[0], 'mediaTypes.video.context') - if (videoMediaType && context !== 'outstream') { - return - } +export const adapter = { + code: 'unruly', + supportedMediaTypes: [ VIDEO ], + isBidRequestValid: function(bid) { + if (!bid) return false; - const payload = { - bidRequests: bidRequestBids - } + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - const bidResponseHandler = createBidResponseHandler(bidRequestBids) - - ajax( - adapter.exchangeUrl, - bidResponseHandler.onBidResponse, - JSON.stringify(payload), - { - contentType: 'application/json', - withCredentials: true - } - ) - } - } + return bid.mediaType === 'video' || context === 'outstream'; + }, - return adapter -} + buildRequests: function(validBidRequests) { + const url = 'https://targeting.unrulymedia.com/prebid'; + const method = 'POST'; + const data = { bidRequests: validBidRequests }; + const options = { contentType: 'application/json' }; -adaptermanager.registerBidAdapter(new UnrulyAdapter(), 'unruly', { - supportedMediaTypes: ['video'] -}); + return { + url, + method, + data, + options, + }; + }, + + interpretResponse: function(serverResponse = {}) { + const serverResponseBody = serverResponse.body; + const noBidsResponse = []; + const isInvalidResponse = !serverResponseBody || !serverResponseBody.bids; + + return isInvalidResponse + ? noBidsResponse + : buildPrebidResponseAndInstallRenderer(serverResponseBody.bids); + } +}; -module.exports = UnrulyAdapter +registerBidder(adapter); diff --git a/modules/unrulyBidAdapter.md b/modules/unrulyBidAdapter.md new file mode 100644 index 00000000000..fc3c6c264be --- /dev/null +++ b/modules/unrulyBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +**Module Name**: Unruly Bid Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prodev@unrulymedia.com + +# Description + +Module that connects to UnrulyX for bids. + +# Test Parameters + +```js + const adUnits = [{ + code: 'ad-slot', + sizes: [[728, 90], [300, 250]], + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [{ + bidder: 'unruly', + params: { + targetingUUID: '6f15e139-5f18-49a1-b52f-87e5e69ee65e', + siteId: 1081534 + } + } + ] + }]; +``` diff --git a/modules/vertamediaBidAdapter.js b/modules/vertamediaBidAdapter.js index d87ae56def1..5876f0b2e7e 100644 --- a/modules/vertamediaBidAdapter.js +++ b/modules/vertamediaBidAdapter.js @@ -1,119 +1,199 @@ -import Adapter from 'src/adapter'; -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; import * as utils from 'src/utils'; -import { ajax } from 'src/ajax'; -import { STATUS } from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; - -const ENDPOINT = '//rtb.vertamedia.com/hb/'; - -function VertamediaAdapter() { - const baseAdapter = new Adapter('vertamedia'); - let bidRequest; - - baseAdapter.callBids = function (bidRequests) { - if (!bidRequests || !bidRequests.bids || bidRequests.bids.length === 0) { - return; - } - - var RTBDataParams = prepareAndSaveRTBRequestParams(bidRequests.bids[0]); - - if (!RTBDataParams) { - return; +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 = '//hb2.vertamedia.com/auction/'; +const OUTSTREAM_SRC = '//player.vertamedia.com/outstream-unit/2.01/outstream.min.js'; +const BIDDER_CODE = 'vertamedia'; +const OUTSTREAM = 'outstream'; +const DISPLAY = 'display'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO, BANNER], + isBidRequestValid: function (bid) { + return bid && bid.params && bid.params.aid; + }, + + /** + * Make a server request from the list of BidRequests + * @param bidRequests + * @param bidderRequest + */ + buildRequests: function (bidRequests, bidderRequest) { + return { + data: bidToTag(bidRequests), + bidderRequest, + method: 'GET', + url: URL + }; + }, + + /** + * Unpack the response from the server into a list of bids + * @param serverResponse + * @param bidderRequest + * @return {Bid[]} An array of bids which were nested inside the server + */ + interpretResponse: function (serverResponse, {bidderRequest}) { + serverResponse = serverResponse.body; + let bids = []; + + if (!utils.isArray(serverResponse)) { + return parseRTBResponse(serverResponse, bidderRequest); } - ajax(ENDPOINT, handleResponse, RTBDataParams, { - contentType: 'text/plain', - withCredentials: true, - method: 'GET' + serverResponse.forEach(serverBidResponse => { + bids = utils.flatten(bids, parseRTBResponse(serverBidResponse, bidderRequest)); }); - }; - function prepareAndSaveRTBRequestParams(bid) { - if (!bid || !bid.params || !bid.params.aid || !bid.placementCode) { - return; - } + return bids; + } +}; - bidRequest = bid; +function parseRTBResponse(serverResponse, bidderRequest) { + const isInvalidValidResp = !serverResponse || !serverResponse.bids || !serverResponse.bids.length; - let size = getSize(bid.sizes); + let bids = []; - bidRequest.width = size.width; - bidRequest.height = size.height; + if (isInvalidValidResp) { + let extMessage = serverResponse && serverResponse.ext && serverResponse.ext.message ? `: ${serverResponse.ext.message}` : ''; + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter ${extMessage}`; - return { - aid: bid.params.aid, - w: size.width, - h: size.height, - domain: document.location.hostname - }; + utils.logError(errorMessage); + + return bids; } - function getSize(requestSizes) { - const parsed = {}; - const size = utils.parseSizesInput(requestSizes)[0]; + serverResponse.bids.forEach(serverBid => { + const requestId = findIndex(bidderRequest.bids, (bidRequest) => { + return bidRequest.bidId === serverBid.requestId; + }); + + if (serverBid.cpm !== 0 && requestId !== -1) { + const bid = createBid(serverBid, getMediaType(bidderRequest.bids[requestId])); - if (typeof size !== 'string') { - return parsed; + bids.push(bid); } + }); - let parsedSize = size.toUpperCase().split('X'); + return bids; +} - return { - width: parseInt(parsedSize[0], 10) || undefined, - height: parseInt(parsedSize[1], 10) || undefined - }; +function bidToTag(bidRequests) { + let tag = { + domain: utils.getTopWindowLocation().hostname + }; + + for (let i = 0, length = bidRequests.length; i < length; i++) { + Object.assign(tag, prepareRTBRequestParams(i, bidRequests[i])); } - /* Notify Prebid of bid responses so bids can get in the auction */ - function handleResponse(response) { - var parsed; + return tag; +} - try { - parsed = JSON.parse(response); - } catch (error) { - utils.logError(error); - } +/** + * Parse mediaType + * @param _index {number} + * @param bid {object} + * @returns {object} + */ +function prepareRTBRequestParams(_index, bid) { + const mediaType = utils.deepAccess(bid, 'mediaTypes.video') ? VIDEO : DISPLAY; + const index = !_index ? '' : `${_index + 1}`; + + return { + ['callbackId' + index]: bid.bidId, + ['aid' + index]: bid.params.aid, + ['ad_type' + index]: mediaType, + ['sizes' + index]: utils.parseSizesInput(bid.sizes).join() + }; +} - if (!parsed || parsed.error || !parsed.bids || !parsed.bids.length) { - bidmanager.addBidResponse(bidRequest.placementCode, createBid(STATUS.NO_BID)); +/** + * Prepare all parameters for request + * @param bidderRequest {object} + * @returns {object} + */ +function getMediaType(bidderRequest) { + const videoMediaType = utils.deepAccess(bidderRequest, 'mediaTypes.video'); + const context = utils.deepAccess(bidderRequest, 'mediaTypes.video.context'); - return; - } + return !videoMediaType ? DISPLAY : context === OUTSTREAM ? OUTSTREAM : VIDEO; +} - bidmanager.addBidResponse(bidRequest.placementCode, createBid(STATUS.GOOD, parsed.bids[0])); +/** + * Configure new bid by response + * @param bidResponse {object} + * @param mediaType {Object} + * @returns {object} + */ +function createBid(bidResponse, mediaType) { + let bid = { + requestId: bidResponse.requestId, + creativeId: bidResponse.cmpId, + height: bidResponse.height, + currency: bidResponse.cur, + width: bidResponse.width, + cpm: bidResponse.cpm, + netRevenue: true, + mediaType, + ttl: 3600 + }; + + if (mediaType === DISPLAY) { + return Object.assign(bid, { + ad: bidResponse.ad + }); } - function createBid(status, tag) { - var bid = bidfactory.createBid(status, tag); + Object.assign(bid, { + vastUrl: bidResponse.vastUrl + }); - bid.code = baseAdapter.getBidderCode(); - bid.bidderCode = bidRequest.bidder; + if (mediaType === OUTSTREAM) { + Object.assign(bid, { + mediaType: 'video', + adResponse: bidResponse, + renderer: newRenderer(bidResponse.requestId) + }); + } - if (!tag || status !== STATUS.GOOD) { - return bid; - } + return bid; +} - bid.mediaType = 'video'; - bid.cpm = tag.cpm; - bid.creative_id = tag.cmpId; - bid.width = bidRequest.width; - bid.height = bidRequest.height; - bid.descriptionUrl = tag.url; - bid.vastUrl = tag.url; +/** + * Create Vertamedia renderer + * @param requestId + * @returns {*} + */ +function newRenderer(requestId) { + const renderer = Renderer.install({ + id: requestId, + url: OUTSTREAM_SRC, + loaded: false + }); - return bid; - } + renderer.setRender(outstreamRender); - return Object.assign(this, { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode - }); + return renderer; } -adaptermanager.registerBidAdapter(new VertamediaAdapter(), 'vertamedia', { - supportedMediaTypes: ['video'] -}); +/** + * Initialise Vertamedia outstream + * @param bid + */ +function outstreamRender(bid) { + bid.renderer.push(() => { + window.VOutstreamAPI.initOutstreams([{ + width: bid.width, + height: bid.height, + vastUrl: bid.vastUrl, + elId: bid.adUnitCode + }]); + }); +} -module.exports = VertamediaAdapter; +registerBidder(spec); diff --git a/modules/vertamediaBidAdapter.md b/modules/vertamediaBidAdapter.md new file mode 100644 index 00000000000..6b1265fa792 --- /dev/null +++ b/modules/vertamediaBidAdapter.md @@ -0,0 +1,65 @@ +# Overview + +**Module Name**: VertaMedia Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: support@verta.media + +# Description + +Get access to multiple demand partners across VertaMedia AdExchange and maximize your yield with VertaMedia header bidding adapter. + +VertaMedia header bidding adapter connects with VertaMedia demand sources in order to fetch bids. +This adapter provides a solution for accessing Video demand and display demand + + +# Test Parameters +``` + var adUnits = [ + + // Video instream adUnit + { + code: 'div-test-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bids: [{ + bidder: 'vertamedia', + params: { + aid: 331133 + } + }] + }, + + // Video outstream adUnit + { + code: 'outstream-test-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [{ + bidder: 'vertamedia', + params: { + aid: 331133 + } + }] + }, + + // Banner adUnit + { + code: 'div-test-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'vertamedia', + params: { + aid: 350975 + } + }] + } + ]; +``` diff --git a/modules/vertozBidAdapter.js b/modules/vertozBidAdapter.js index b6966dd62d1..f3727714454 100644 --- a/modules/vertozBidAdapter.js +++ b/modules/vertozBidAdapter.js @@ -1,20 +1,28 @@ -var CONSTANTS = require('src/constants.json'); -var utils = require('src/utils.js'); -var bidfactory = require('src/bidfactory.js'); -var bidmanager = require('src/bidmanager.js'); -var adloader = require('src/adloader.js'); -var adaptermanager = require('src/adaptermanager'); +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'vertoz'; +const BASE_URI = '//hb.vrtzads.com/vzhbidder/bid?'; -function VertozAdapter() { - const BASE_URI = '//hb.vrtzads.com/vzhbidder/bid?'; - const BIDDER_NAME = 'vertoz'; - const QUERY_PARAM_KEY = 'q'; - - function _callBids(params) { - var bids = params.bids || []; - - for (var i = 0; i < bids.length; i++) { - var bid = bids[i]; +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. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequestsArr) { + var bidRequests = bidRequestsArr || []; + return bidRequests.map(bid => { let slotBidId = utils.getValue(bid, 'bidId'); let cb = Math.round(new Date().getTime() / 1000); let vzEndPoint = BASE_URI; @@ -23,7 +31,7 @@ function VertozAdapter() { let cpm = utils.getValue(reqParams, 'cpmFloor'); if (utils.isEmptyStr(placementId)) { - utils.logError('missing params:', BIDDER_NAME, 'Enter valid vzPlacementId'); + utils.logError('missing params:', BIDDER_CODE, 'Enter valid vzPlacementId'); return; } @@ -37,36 +45,42 @@ function VertozAdapter() { _cbn: '$$PREBID_GLOBAL$$' }; - let queryParamValue = JSON.stringify(vzReq); - vzEndPoint = utils.tryAppendQueryString(vzEndPoint, QUERY_PARAM_KEY, queryParamValue); - adloader.loadScript(vzEndPoint); - } - } + let queryParamValue = encodeURIComponent(JSON.stringify(vzReq)); - $$PREBID_GLOBAL$$.vzResponse = function (vertozResponse) { - var bidRespObj = vertozResponse; - var bidObject; - var reqBidObj = utils.getBidRequest(bidRespObj.slotBidId); + return { + method: 'POST', + data: {q: queryParamValue}, + url: vzEndPoint + }; + }) + }, + /** + * 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) { + var bidRespObj = serverResponse.body; + const bidResponses = []; if (bidRespObj.cpm) { - bidObject = bidfactory.createBid(CONSTANTS.STATUS.GOOD, reqBidObj); - bidObject.cpm = Number(bidRespObj.cpm); - bidObject.ad = bidRespObj.ad + utils.createTrackPixelHtml(decodeURIComponent(bidRespObj.nurl)); - bidObject.width = bidRespObj.adWidth; - bidObject.height = bidRespObj.adHeight; - } else { - let respStatusText = bidRespObj.statusText; - bidObject = bidfactory.createBid(CONSTANTS.STATUS.NO_BID, reqBidObj); - utils.logMessage(respStatusText); + const bidResponse = { + requestId: bidRespObj.slotBidId, + cpm: Number(bidRespObj.cpm), + width: Number(bidRespObj.adWidth), + height: Number(bidRespObj.adHeight), + netRevenue: true, + mediaType: 'banner', + currency: 'USD', + dealId: null, + creativeId: null, + ttl: 300, + ad: bidRespObj.ad + utils.createTrackPixelHtml(decodeURIComponent(bidRespObj.nurl)) + }; + bidResponses.push(bidResponse); } - - var adSpaceId = reqBidObj.placementCode; - bidObject.bidderCode = BIDDER_NAME; - bidmanager.addBidResponse(adSpaceId, bidObject); - }; - return { callBids: _callBids }; + return bidResponses; + } } - -adaptermanager.registerBidAdapter(new VertozAdapter(), 'vertoz'); - -module.exports = VertozAdapter; +registerBidder(spec); diff --git a/modules/vertozBidAdapter.md b/modules/vertozBidAdapter.md new file mode 100644 index 00000000000..100492da58b --- /dev/null +++ b/modules/vertozBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Vertoz Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid-team@vertoz.com +``` + +# Description + +Connects to Vertoz exchange for bids. +Vertoz Bidder adapter supports Banner ads. +Use bidder code ```vertoz``` for all Vertoz traffic. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + sizes: [[300, 250], [300,600]], // a display size(s) + bids: [{ + bidder: 'vertoz', + params: { + placementId: 'VZ-HB-B784382V6C6G3C' + } + }] + }, +]; +``` + diff --git a/modules/viBidAdapter.js b/modules/viBidAdapter.js new file mode 100644 index 00000000000..bcfc4e246ac --- /dev/null +++ b/modules/viBidAdapter.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 = 'vi'; +const SUPPORTED_MEDIA_TYPES = [BANNER]; + +function isBidRequestValid(bid) { + return !!(bid.params.pubId); +} + +function buildRequests(bidReqs) { + let imps = []; + utils._each(bidReqs, function (bid) { + imps.push({ + id: bid.bidId, + sizes: utils.parseSizesInput(bid.sizes).map(size => size.split('x')), + bidFloor: parseFloat(bid.params.bidFloor) > 0 ? bid.params.bidFloor : 0 + }); + }); + + const bidRequest = { + id: bidReqs[0].requestId, + imps: imps, + publisherId: utils.getBidIdParameter('pubId', bidReqs[0].params), + siteId: utils.getBidIdParameter('siteId', bidReqs[0].params), + cat: utils.getBidIdParameter('cat', bidReqs[0].params), + language: utils.getBidIdParameter('lang', bidReqs[0].params), + domain: utils.getTopWindowLocation().hostname, + page: utils.getTopWindowUrl(), + referrer: utils.getTopWindowReferrer() + }; + return { + method: 'POST', + url: `//pb.vi-serve.com/prebid/bid`, + data: JSON.stringify(bidRequest), + options: {contentType: 'application/json', withCredentials: false} + }; +} + +function interpretResponse(bids) { + let responses = []; + utils._each(bids.body, function(bid) { + responses.push({ + requestId: bid.id, + cpm: parseFloat(bid.price), + width: parseInt(bid.width, 10), + height: parseInt(bid.height, 10), + creativeId: bid.creativeId, + dealId: bid.dealId || null, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: decodeURIComponent(`${bid.ad}`), + ttl: 60000 + }); + }); + return responses; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + isBidRequestValid, + buildRequests, + interpretResponse +} + +registerBidder(spec); diff --git a/modules/viBidAdapter.md b/modules/viBidAdapter.md new file mode 100644 index 00000000000..23288024fcc --- /dev/null +++ b/modules/viBidAdapter.md @@ -0,0 +1,41 @@ +# Overview + +``` +Module Name: vi bid adapter +Module Type: Bidder adapter +Maintainer: support@vi.ai +``` + +# Description + +The video intelligence (vi) adapter integration to the Prebid library. +Connects to vi’s demand sources. +There should be only one ad unit with vi bid adapter on each single page. + +# Test Parameters + +``` +var adUnits = [{ + code: 'div-0', + sizes: [[320, 480]], + bids: [{ + bidder: 'vi', + params: { + pubId: 'sb_test', + lang: 'en-US', + cat: 'IAB1', + bidFloor: 0.05 //optional + } + }] +}]; +``` + +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :---------------------------------------------- | :--------------------------------- | +| `pubId` | required | Publisher ID, provided by vi | 'sb_test' | +| `lang` | required | Ad language, in ISO 639-1 language code format | 'en-US', 'es-ES', 'de' | +| `cat` | required | Ad IAB category (top-level or subcategory), single one supported | 'IAB1', 'IAB9-1' | +| `bidFloor` | optional | Lowest value of expected bid price | 0.001 | + diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js new file mode 100644 index 00000000000..bcf8856e049 --- /dev/null +++ b/modules/vidazooBidAdapter.js @@ -0,0 +1,134 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import {BANNER} from 'src/mediaTypes'; +export const URL = '//prebid.nininin.com'; +const BIDDER_CODE = 'vidazoo'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const INTERNAL_SYNC_TYPE = { + IFRAME: 'iframe', + IMAGE: 'img' +}; +const EXTERNAL_SYNC_TYPE = { + IFRAME: 'iframe', + IMAGE: 'image' +}; + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(params.cId && params.pId); +} + +function buildRequest(bid, topWindowUrl, size) { + const {params, bidId} = bid; + const {bidFloor, cId, pId, ext} = params; + // Prebid's util function returns AppNexus style sizes (i.e. 300x250) + const [width, height] = size.split('x'); + + const dto = { + method: 'GET', + url: `${URL}/prebid/${cId}`, + data: { + url: topWindowUrl, + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + publisherId: pId, + width, + height + } + } + + utils._each(ext, (value, key) => { + dto.data['ext.' + key] = value; + }); + + return dto; +} + +function buildRequests(validBidRequests) { + const topWindowUrl = utils.getTopWindowUrl(); + const requests = []; + validBidRequests.forEach(validBidRequest => { + const sizes = utils.parseSizesInput(validBidRequest.sizes); + sizes.forEach(size => { + const request = buildRequest(validBidRequest, topWindowUrl, size); + requests.push(request); + }); + }); + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + const {creativeId, ad, price, exp} = serverResponse.body; + if (!ad || !price) { + return []; + } + const {bidId, width, height} = request.data; + try { + return [{ + requestId: bidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + ad: ad + }]; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses) { + const {iframeEnabled, pixelEnabled} = syncOptions; + + if (iframeEnabled) { + return [{ + type: 'iframe', + url: '//static.nininin.com/basev/sync/user_sync.html' + }]; + } + + if (pixelEnabled) { + const lookup = {}; + const syncs = []; + responses.forEach(response => { + const {body} = response; + const cookies = body ? body.cookies || [] : []; + cookies.forEach(cookie => { + switch (cookie.type) { + case INTERNAL_SYNC_TYPE.IFRAME: + break; + case INTERNAL_SYNC_TYPE.IMAGE: + if (pixelEnabled && !lookup[cookie.src]) { + syncs.push({ + type: EXTERNAL_SYNC_TYPE.IMAGE, + url: cookie.src + }); + } + break; + } + }); + }); + return syncs; + } + + return []; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/vidazooBidAdapter.md b/modules/vidazooBidAdapter.md new file mode 100644 index 00000000000..1e9dc47dd51 --- /dev/null +++ b/modules/vidazooBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +**Module Name:** Vidazoo Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** server-dev@getintent.com + +# Description + +Module that connects to Vidazoo's demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'vidazoo', + params: { + cId: '5a1c419d95fce900044c334e', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js new file mode 100755 index 00000000000..1fad6cd8337 --- /dev/null +++ b/modules/visxBidAdapter.js @@ -0,0 +1,140 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; +const BIDDER_CODE = 'visx'; +const ENDPOINT_URL = '//t.visx.net/hb'; +const TIME_TO_LIVE = 360; +const DEFAULT_CUR = 'EUR'; +const ADAPTER_SYNC_URL = '//t.visx.net/push_sync'; +const LOG_ERROR_MESS = { + noAuid: 'Bid from response has no auid parameter - ', + noAdm: 'Bid from response has no adm parameter - ', + noBid: 'Array of bid objects is empty', + noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', + emptyUids: 'Uids should not be empty', + emptySeatbid: 'Seatbid array from response has an empty item', + emptyResponse: 'Response is empty', + hasEmptySeatbidArray: 'Response has empty seatbid array', + hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' +}; +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + return !!bid.params.uid; + }, + buildRequests: function(validBidRequests) { + const auids = []; + const bidsMap = {}; + const bids = validBidRequests || []; + const currency = + config.getConfig(`currency.bidderCurrencyDefault.${BIDDER_CODE}`) || + config.getConfig('currency.adServerCurrency') || + DEFAULT_CUR; + let priceType = 'net'; + let reqId; + + bids.forEach(bid => { + if (bid.params.priceType === 'gross') { + priceType = 'gross'; + } + if (!bidsMap[bid.params.uid]) { + bidsMap[bid.params.uid] = [bid]; + auids.push(bid.params.uid); + } else { + bidsMap[bid.params.uid].push(bid); + } + reqId = bid.bidderRequestId; + }); + + const payload = { + u: utils.getTopWindowUrl(), + pt: priceType, + auids: auids.join(','), + test: 1, + r: reqId, + cur: currency, + }; + + return { + method: 'GET', + url: ENDPOINT_URL, + data: payload, + bidsMap: bidsMap, + }; + }, + interpretResponse: function(serverResponse, bidRequest) { + serverResponse = serverResponse && serverResponse.body; + const bidResponses = []; + const bidsMap = bidRequest.bidsMap; + const priceType = bidRequest.data.pt; + const currency = bidRequest.data.cur; + + let errorMessage; + + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; + } + + if (!errorMessage && serverResponse.seatbid) { + serverResponse.seatbid.forEach(respItem => { + _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, currency, bidResponses); + }); + } + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + }, + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: ADAPTER_SYNC_URL + }]; + } + } +}; + +function _getBidFromResponse(respItem) { + if (!respItem) { + utils.logError(LOG_ERROR_MESS.emptySeatbid); + } else if (!respItem.bid) { + utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + } else if (!respItem.bid[0]) { + utils.logError(LOG_ERROR_MESS.noBid); + } + return respItem && respItem.bid && respItem.bid[0]; +} + +function _addBidResponse(serverBid, bidsMap, priceType, currency, bidResponses) { + if (!serverBid) return; + let errorMessage; + if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); + if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + awaitingBids.forEach(bid => { + const bidResponse = { + requestId: bid.bidId, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, + currency: currency || DEFAULT_CUR, + netRevenue: priceType !== 'gross', + ttl: TIME_TO_LIVE, + ad: serverBid.adm, + dealId: serverBid.dealid + }; + bidResponses.push(bidResponse); + }); + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } + if (errorMessage) { + utils.logError(errorMessage); + } +} + +registerBidder(spec); diff --git a/modules/visxBidAdapter.md b/modules/visxBidAdapter.md new file mode 100755 index 00000000000..7d5981132c2 --- /dev/null +++ b/modules/visxBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +``` +Module Name: VIS.X Bidder Adapter +Module Type: Bidder Adapter +Maintainer: service@yoc.com +``` + +# Description + +Module that connects to VIS.X demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [ + // YOC Mystery Ad adUnit + { + code: 'yma-test-div', + sizes: [[1, 1]], + bids: [ + { + bidder: 'visx', + params: { + uid: '903535' + } + } + ] + }, + // YOC Understitial Ad adUnit + { + code: 'yua-test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: 'visx', + params: { + uid: '903536' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/vubleAnalyticsAdapter.js b/modules/vubleAnalyticsAdapter.js new file mode 100644 index 00000000000..5bd27b1c0de --- /dev/null +++ b/modules/vubleAnalyticsAdapter.js @@ -0,0 +1,258 @@ +/** + * vuble.js - Vuble Prebid Analytics Adapter + */ + +import adapter from 'src/AnalyticsAdapter'; +import adaptermanager from 'src/adaptermanager'; +import CONSTANTS from 'src/constants.json'; +import {ajax} from '../src/ajax'; +import * as utils from '../src/utils'; + +const ANALYTICS_VERSION = '1.0.0'; +const DEFAULT_QUEUE_TIMEOUT = 4000; +const DEFAULT_HOST = 'player.mediabong'; +const analyticsType = 'endpoint'; + +const EVENTS = [ + CONSTANTS.EVENTS.AUCTION_INIT, + CONSTANTS.EVENTS.AUCTION_END, + CONSTANTS.EVENTS.BID_REQUESTED, + CONSTANTS.EVENTS.BID_RESPONSE, + CONSTANTS.EVENTS.BID_WON, + CONSTANTS.EVENTS.BID_TIMEOUT, +]; + +var vubleAnalytics = Object.assign(adapter({ analyticsType: analyticsType, }), + { + track: function({ eventType, args }) { + if (!vubleAnalytics.context) { + return; + } + if (EVENTS.indexOf(eventType) !== -1) { + if (eventType === CONSTANTS.EVENTS.AUCTION_INIT && + vubleAnalytics.context.queue) { + vubleAnalytics.context.queue.init(); + } + + let events = deal[eventType](args); + + if (vubleAnalytics.context.queue) { + vubleAnalytics.context.queue.push(events); + } + if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + sendAll(); + } + } + } + }); + +vubleAnalytics.context = {}; + +vubleAnalytics.originEnableAnalytics = vubleAnalytics.enableAnalytics; + +vubleAnalytics.enableAnalytics = config => { + if (!config.options.pubId) { + utils.logError('The publisher id is not defined. Analytics won\'t work'); + + return; + } + + if (!config.options.host) { + if (!config.options.env) { + utils.logError('The environement is not defined. Analytics won\'t work'); + + return; + } + config.options.host = DEFAULT_HOST + '.' + config.options.env + '/t'; + } + + vubleAnalytics.context = { + host: config.options.host, + pubId: config.options.pubId, + requestTemplate: buildRequestTemplate(config.options.pubId), + queue: new ExpiringQueue( + sendAll, + config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT + ), + }; + vubleAnalytics.originEnableAnalytics(config); +}; + +adaptermanager.registerAnalyticsAdapter({ + adapter: vubleAnalytics, + code: 'vuble' +}); + +export default vubleAnalytics; + +function sendAll() { + let events = vubleAnalytics.context.queue.popAll(); + if (events.length !== 0) { + let req = Object.assign( + {}, + vubleAnalytics.context.requestTemplate, + {rtb: events} + ); + ajax( + `//${vubleAnalytics.context.host}/rtb.php`, + undefined, + JSON.stringify(req) + ); + } +} + +var deal = +{ + auctionInit() { + vubleAnalytics.context.auctionTimeStart = Date.now(); + return [{ + event: CONSTANTS.EVENTS.AUCTION_INIT, + date: vubleAnalytics.context.auctionTimeStart, + }]; + }, + + bidRequested(args) { + return args.bids.map( + function(bid) { + let vubleEvent = { event: CONSTANTS.EVENTS.BID_REQUESTED }; + + if (typeof args.bidderCode !== 'undefined') { + vubleEvent.adapter = args.bidderCode + } + if (typeof bid.bidId !== 'undefined') { + vubleEvent.bidder = bid.bidId; + } + if (typeof bid.bidderRequestId !== 'undefined') { + vubleEvent.id = bid.bidderRequestId; + } + if (typeof bid.params.floorPrice !== 'undefined') { + vubleEvent.floor = bid.params.floorPrice; + } + if (typeof bid.params.zoneId !== 'undefined') { + vubleEvent.zoneId = bid.params.zoneId; + } + if (typeof bid.mediaTypes !== 'undefined' && + typeof bid.mediaTypes.videos !== 'undefined' && + typeof bid.mediaTypes.videos.context !== 'undefined') { + vubleEvent.context = bid.mediaTypes.videos.context; + } + if (typeof bid.sizes !== 'undefined') { + vubleEvent.size = bid.sizes; + } + + return vubleEvent; + } + ); + }, + + bidResponse(args) { + const event = formalizeBidEvent( + args.bidderCode, + CONSTANTS.EVENTS.BID_RESPONSE, + args.cpm, + args.dealId, + args.adId + ); + + return [event]; + }, + + bidWon(args) { + const event = formalizeBidEvent( + args.bidderCode, + CONSTANTS.EVENTS.BID_WON, + args.cpm, + args.dealId, + ); + + return [event]; + }, + + auctionEnd() { + return [{ + event: CONSTANTS.EVENTS.AUCTION_END, + time: (Date.now() - vubleAnalytics.context.auctionTimeStart) / 1000, + }]; + }, + + bidTimeout(args) { + return args.map((bid) => { + return { + adapter: bid, + event: CONSTANTS.EVENTS.BID_TIMEOUT, + }; + }); + } +}; + +function formalizeBidEvent(adapter, event, value = 0, dealId = 0, id = 0) { + let vubleEvent = { event: event }; + + if (adapter) { + vubleEvent.adapter = adapter + } + if (value) { + vubleEvent.val = value; + } + if (dealId) { + vubleEvent.id = dealId; + } + if (id) { + vubleEvent.id = id; + } + + return vubleEvent; +} + +function buildRequestTemplate(pubId) { + const topLocation = utils.getTopWindowLocation(); + + return { + ver: ANALYTICS_VERSION, + domain: topLocation.hostname, + path: topLocation.pathname, + pubid: pubId, + width: window.screen.width, + height: window.screen.height, + lang: navigator.language, + } +} + +/** + * Expiring queue implementation + * @param callback + * @param time + */ +export function ExpiringQueue(callback, time) { + let queue = []; + let timeoutId; + + this.push = event => { + if (event instanceof Array) { + queue.push.apply(queue, event); + } else { + queue.push(event); + } + reset(); + }; + + this.popAll = () => { + let result = queue; + queue = []; + reset(); + return result; + }; + + this.init = reset; + + function reset() { + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(() => { + if (queue.length) { + callback(); + } + }, time); + } +} diff --git a/modules/vubleAnalyticsAdapter.md b/modules/vubleAnalyticsAdapter.md new file mode 100644 index 00000000000..dfe0a8d8eb0 --- /dev/null +++ b/modules/vubleAnalyticsAdapter.md @@ -0,0 +1,23 @@ +# Overview + +Module Name: Vuble Analytics Adapter + +Module Type: Vuble Analytics Adapter + +Maintainer: abruyere@mediabong.com + +# Description + +Analytics adapter for vuble.tv Contact contact@mediabong.com for information. + +# Test Parameters + +``` +{ + provider: 'vuble', + options: { + pubId: 18, // require + env: 'net', // require + } +} +``` diff --git a/modules/vubleBidAdapter.js b/modules/vubleBidAdapter.js new file mode 100644 index 00000000000..a61b80777fc --- /dev/null +++ b/modules/vubleBidAdapter.js @@ -0,0 +1,137 @@ +// Vuble Adapter + +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'vuble'; + +const ENVS = ['com', 'net']; +const CURRENCIES = { + com: 'EUR', + net: 'USD' +}; +const TTL = 60; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['video'], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (utils.isEmpty(bid.sizes) || utils.parseSizesInput(bid.sizes).length == 0) { + return false; + } + + if (!utils.deepAccess(bid, 'mediaTypes.video.context')) { + return false; + } + + if (!utils.contains(ENVS, bid.params.env)) { + return false; + } + + return !!(bid.params.env && bid.params.pubId && bid.params.zoneId); + }, + + /** + * 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) { + return validBidRequests.map(bid => { + // We take the first size + let size = utils.parseSizesInput(bid.sizes)[0].split('x'); + + // Get the page's url + let referrer = utils.getTopWindowUrl(); + if (bid.params.referrer) { + referrer = bid.params.referrer; + } + + // Get Video Context + let context = utils.deepAccess(bid, 'mediaTypes.video.context'); + + let url = '//player.mediabong.' + bid.params.env + '/prebid/request'; + let data = { + width: size[0], + height: size[1], + pub_id: bid.params.pubId, + zone_id: bid.params.zoneId, + context: context, + floor_price: bid.params.floorPrice ? bid.params.floorPrice : 0, + url: referrer, + env: bid.params.env, + bid_id: bid.bidId + }; + + return { + method: 'POST', + url: url, + data: data + }; + }); + }, + + /** + * 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, bid) { + const responseBody = serverResponse.body; + + if (typeof responseBody !== 'object' || responseBody.status !== 'ok') { + return []; + } + + let responses = []; + let reponse = { + requestId: bid.data.bid_id, + cpm: responseBody.cpm, + width: bid.data.width, + height: bid.data.height, + ttl: TTL, + creativeId: responseBody.creativeId, + dealId: responseBody.dealId, + netRevenue: true, + currency: CURRENCIES[bid.data.env], + vastUrl: responseBody.url, + mediaType: 'video' + }; + responses.push(reponse); + + return responses; + }, + + /** + * 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) { + if (serverResponses.length > 0) { + let responseBody = serverResponses[0].body; + if (typeof responseBody !== 'object' || responseBody.iframeSync) { + return [{ + type: 'iframe', + url: responseBody.iframeSync + }]; + } + } + } + return []; + } +}; + +registerBidder(spec); diff --git a/modules/vubleBidAdapter.md b/modules/vubleBidAdapter.md new file mode 100644 index 00000000000..6bd8d3f779a --- /dev/null +++ b/modules/vubleBidAdapter.md @@ -0,0 +1,58 @@ +# Overview + +``` +Module Name: Vuble Bidder Adapter +Module Type: Vuble Adapter +Maintainer: gv@mediabong.com +``` + +# Description + +Module that connects to Vuble's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-video-instream', + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bids: [ + { + bidder: "vuble", + params: { + env: 'net', + pubId: '18', + zoneId: '12345', + referrer: "http://www.vuble.tv/", // optional + floorPrice: 5.00 // optional + } + } + ] + }, + { + code: 'test-video-outstream', + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [ + { + bidder: "vuble", + params: { + env: 'net', + pubId: '18', + zoneId: '12345', + referrer: "http://www.vuble.tv/", // optional + floorPrice: 5.00 // optional + } + } + ] + } + ]; \ No newline at end of file diff --git a/modules/wideorbitBidAdapter.js b/modules/wideorbitBidAdapter.js deleted file mode 100644 index f0ed885f6a3..00000000000 --- a/modules/wideorbitBidAdapter.js +++ /dev/null @@ -1,223 +0,0 @@ -const bidfactory = require('src/bidfactory.js'); -const bidmanager = require('src/bidmanager.js'); -const utils = require('src/utils.js'); -const adloader = require('src/adloader'); -const adaptermanager = require('src/adaptermanager'); - -function WideOrbitAdapter() { - const pageImpression = 'JSAdservingMP.ashx?pc={pc}&pbId={pbId}&clk=&exm=&jsv=1.0&tsv=1.0&cts={cts}&arp=0&fl=0&vitp=&vit=&jscb=window.$$PREBID_GLOBAL$$.handleWideOrbitCallback&url={referrer}&fp=&oid=&exr=&mraid=&apid=&apbndl=&mpp=0&uid=&cb={cb}&hb=1'; - const pageRepeatCommonParam = '&gid{o}={gid}&pp{o}=&clk{o}=&rpos{o}={rpos}&ecpm{o}={ecpm}&ntv{o}=&ntl{o}=&adsid{o}='; - const pageRepeatParamId = '&pId{o}={pId}&rank{o}={rank}'; - const pageRepeatParamNamed = '&wsName{o}={wsName}&wName{o}={wName}&rank{o}={rank}&bfDim{o}={width}x{height}&subp{o}={subp}'; - const base = (window.location.protocol) + '//p{pbId}.atemda.com/'; - let bids; - const adapterName = 'wideorbit'; - - function _fixParamNames(param) { - if (!param) { - return; - } - - const properties = ['site', 'page', 'width', 'height', 'rank', 'subPublisher', 'ecpm', 'atf', 'pId', 'pbId', 'referrer']; - let prop; - - utils._each(properties, function (correctName) { - for (prop in param) { - if (param.hasOwnProperty(prop) && prop.toLowerCase() === correctName.toLowerCase()) { - param[correctName] = param[prop]; - break; - } - } - }); - } - - function _setParam(str, param, value) { - var pattern = new RegExp('{' + param + '}', 'g'); - - if (value === true) { - value = 1; - } - if (value === false) { - value = 0; - } - return str.replace(pattern, value); - } - - function _setParams(str, keyValuePairs) { - utils._each(keyValuePairs, function (keyValuePair) { - str = _setParam(str, keyValuePair[0], keyValuePair[1]); - }); - return str; - } - - function _setCommonParams(pos, params) { - return _setParams(pageRepeatCommonParam, [ - ['o', pos], - ['gid', encodeURIComponent(params.tagId)], - ['rpos', params.atf ? 1001 : 0], - ['ecpm', params.ecpm || ''] - ]); - } - - function _getRankParam(rank, pos) { - return rank || pos; - } - - function _setupIdPlacementParameters(pos, params) { - return _setParams(pageRepeatParamId, [ - ['o', pos], - ['pId', params.pId], - ['rank', _getRankParam(params.rank, pos)] - ]); - } - - function _setupNamedPlacementParameters(pos, params) { - return _setParams(pageRepeatParamNamed, [ - ['o', pos], - ['wsName', encodeURIComponent(decodeURIComponent(params.site))], - ['wName', encodeURIComponent(decodeURIComponent(params.page))], - ['width', params.width], - ['height', params.height], - ['subp', params.subPublisher ? encodeURIComponent(decodeURIComponent(params.subPublisher)) : ''], - ['rank', _getRankParam(params.rank, pos)] - ]); - } - - function _setupAdCall(publisherId, placementCount, placementsComponent, referrer) { - return _setParams(base + pageImpression, [ - ['pbId', publisherId], - ['pc', placementCount], - ['cts', new Date().getTime()], - ['cb', Math.floor(Math.random() * 100000000)], - ['referrer', encodeURIComponent(referrer || '')] - ]) + placementsComponent; - } - - function _setupPlacementParameters(pos, params) { - var commonParams = _setCommonParams(pos, params); - - if (params.pId) { - return _setupIdPlacementParameters(pos, params) + commonParams; - } - - return _setupNamedPlacementParameters(pos, params) + commonParams; - } - - function _callBids(params) { - let publisherId; - let bidUrl = ''; - let i; - let referrer; - - bids = params.bids || []; - - for (i = 0; i < bids.length; i++) { - var requestParams = bids[i].params; - - requestParams.tagId = bids[i].placementCode; - - _fixParamNames(requestParams); - - publisherId = requestParams.pbId; - referrer = referrer || requestParams.referrer; - bidUrl += _setupPlacementParameters(i, requestParams); - } - - bidUrl = _setupAdCall(publisherId, bids.length, bidUrl, referrer); - - utils.logMessage('Calling WO: ' + bidUrl); - - adloader.loadScript(bidUrl); - } - - function _processUserMatchings(userMatchings) { - const headElem = document.getElementsByTagName('head')[0]; - let createdElem; - - utils._each(userMatchings, function (userMatching) { - createdElem = undefined; - switch (userMatching.Type) { - case 'redirect': - createdElem = document.createElement('img'); - break; - case 'iframe': - createdElem = utils.createInvisibleIframe(); - break; - case 'js': - createdElem = document.createElement('script'); - createdElem.type = 'text/javascript'; - createdElem.async = true; - break; - } - if (createdElem) { - createdElem.src = decodeURIComponent(userMatching.Url); - headElem.insertBefore(createdElem, headElem.firstChild); - } - }); - } - - function _getBidResponse(id, placements) { - var i; - - for (i = 0; i < placements.length; i++) { - if (placements[i].ExtPlacementId === id) { - return placements[i]; - } - } - } - - function _isUrl(scr) { - return scr.slice(0, 6) === 'http:/' || scr.slice(0, 7) === 'https:/' || scr.slice(0, 2) === '//'; - } - - function _buildAdCode(placement) { - let adCode = placement.Source; - let pixelTag; - - utils._each(placement.TrackingCodes, function (trackingCode) { - if (_isUrl(trackingCode)) { - pixelTag = ''; - } else { - pixelTag = trackingCode; - } - adCode = pixelTag + adCode; - }); - - return adCode; - } - - window.$$PREBID_GLOBAL$$ = window.$$PREBID_GLOBAL$$ || {}; - window.$$PREBID_GLOBAL$$.handleWideOrbitCallback = function (response) { - var bidResponse, - bidObject; - - utils.logMessage('WO response. Placements: ' + response.Placements.length); - - _processUserMatchings(response.UserMatchings); - - utils._each(bids, function (bid) { - bidResponse = _getBidResponse(bid.placementCode, response.Placements); - - if (bidResponse && bidResponse.Type === 'DirectHTML') { - bidObject = bidfactory.createBid(1); - bidObject.cpm = bidResponse.Bid; - bidObject.ad = _buildAdCode(bidResponse); - bidObject.width = bidResponse.Width; - bidObject.height = bidResponse.Height; - } else { - bidObject = bidfactory.createBid(2); - } - - bidObject.bidderCode = adapterName; - bidmanager.addBidResponse(bid.placementCode, bidObject); - }); - }; - - return { - callBids: _callBids - }; -} - -adaptermanager.registerBidAdapter(new WideOrbitAdapter(), 'wideorbit'); - -module.exports = WideOrbitAdapter; diff --git a/modules/widespaceBidAdapter.js b/modules/widespaceBidAdapter.js index c9e6fb4a11d..7905bd8d28c 100644 --- a/modules/widespaceBidAdapter.js +++ b/modules/widespaceBidAdapter.js @@ -1,127 +1,241 @@ +import { version } from '../package.json'; +import { config } from 'src/config'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { + cookiesAreEnabled, + parseQueryStringParameters, + parseSizesInput, + getTopWindowReferrer +} from 'src/utils'; +import includes from 'core-js/library/fn/array/includes'; +import find from 'core-js/library/fn/array/find'; + +const BIDDER_CODE = 'widespace'; +const WS_ADAPTER_VERSION = '2.0.0'; +const LOCAL_STORAGE_AVAILABLE = window.localStorage; +const COOKIE_ENABLED = cookiesAreEnabled(); +const LS_KEYS = { + PERF_DATA: 'wsPerfData', + LC_UID: 'wsLcuid', + CUST_DATA: 'wsCustomData' +}; + +let preReqTime = 0; + +export const spec = { + code: BIDDER_CODE, + + supportedMediaTypes: ['banner'], + + isBidRequestValid: function(bid) { + if (bid.params && bid.params.sid) { + return true; + } + return false; + }, + + buildRequests: function(validBidRequests) { + let serverRequests = []; + const REQUEST_SERVER_URL = getEngineUrl(); + const DEMO_DATA_PARAMS = ['gender', 'country', 'region', 'postal', 'city', 'yob']; + const PERF_DATA = getData(LS_KEYS.PERF_DATA).map(perf_data => JSON.parse(perf_data)); + const CUST_DATA = getData(LS_KEYS.CUST_DATA, false)[0]; + const LC_UID = getLcuid(); + + let isInHostileIframe = false; + try { + window.top.location.toString(); + isInHostileIframe = false; + } catch (e) { + isInHostileIframe = true; + } -const utils = require('src/utils.js'); -const adloader = require('src/adloader.js'); -const bidmanager = require('src/bidmanager.js'); -const bidfactory = require('src/bidfactory.js'); -const adaptermanager = require('src/adaptermanager'); -const WS_ADAPTER_VERSION = '1.0.3'; - -function WidespaceAdapter() { - const useSSL = document.location.protocol === 'https:'; - const baseURL = (useSSL ? 'https:' : 'http:') + '//engine.widespace.com/map/engine/hb/dynamic?'; - const callbackName = '$$PREBID_GLOBAL$$.widespaceHandleCB'; - - function _callBids(params) { - let bids = (params && params.bids) || []; - - for (var i = 0; i < bids.length; i++) { - const bid = bids[i]; - const callbackUid = bid.bidId; - const sid = bid.params.sid; - const currency = bid.params.cur || bid.params.currency; - - // Handle Sizes string - let sizeQueryString = ''; - let parsedSizes = utils.parseSizesInput(bid.sizes); - - sizeQueryString = parsedSizes.reduce((prev, curr) => { - return prev ? `${prev},${curr}` : curr; - }, sizeQueryString); - - let requestURL = baseURL; - requestURL = utils.tryAppendQueryString(requestURL, 'hb.ver', WS_ADAPTER_VERSION); - - const params = { + validBidRequests.forEach((bid, i) => { + let data = { + 'screenWidthPx': screen && screen.width, + 'screenHeightPx': screen && screen.height, + 'adSpaceHttpRefUrl': getTopWindowReferrer(), + 'referer': (isInHostileIframe ? window : window.top).location.href.split('#')[0], + 'inFrame': 1, + 'sid': bid.params.sid, + 'lcuid': LC_UID, + 'vol': isInHostileIframe ? '' : visibleOnLoad(document.getElementById(bid.adUnitCode)), 'hb': '1', - 'hb.name': 'prebidjs', - 'hb.callback': callbackName, - 'hb.callbackUid': callbackUid, - 'hb.sizes': sizeQueryString, - 'hb.currency': currency, - 'sid': sid + 'hb.cd': CUST_DATA ? encodedParamValue(CUST_DATA) : '', + 'hb.floor': bid.bidfloor || '', + 'hb.spb': i === 0 ? pixelSyncPossibility() : -1, + 'hb.ver': WS_ADAPTER_VERSION, + 'hb.name': `prebidjs-${version}`, + 'hb.bidId': bid.bidId, + 'hb.sizes': parseSizesInput(bid.sizes).join(','), + 'hb.currency': bid.params.cur || bid.params.currency || '' }; + // Include demo data if (bid.params.demo) { - let demoFields = ['gender', 'country', 'region', 'postal', 'city', 'yob']; - for (let i = 0; i < demoFields.length; i++) { - if (!bid.params.demo[demoFields[i]]) { - continue; + DEMO_DATA_PARAMS.forEach((key) => { + if (bid.params.demo[key]) { + data[key] = bid.params.demo[key]; } - params['hb.demo.' + demoFields[i]] = bid.params.demo[demoFields[i]]; - } + }); } - requestURL += '#'; - - var paramKeys = Object.keys(params); - - for (var k = 0; k < paramKeys.length; k++) { - var key = paramKeys[k]; - requestURL += key + '=' + params[key] + '&'; + // Include performance data + if (PERF_DATA[i]) { + Object.keys(PERF_DATA[i]).forEach((perfDataKey) => { + data[perfDataKey] = PERF_DATA[i][perfDataKey]; + }); } - // Expose the callback - $$PREBID_GLOBAL$$.widespaceHandleCB = window[callbackName] = handleCallback; - - adloader.loadScript(requestURL); - } - } - - // Handle our callback - var handleCallback = function handleCallback(bidsArray) { - if (!bidsArray) { return; } - - let bidObject; - let bidCode = 'widespace'; - - for (var i = 0, l = bidsArray.length; i < l; i++) { - const bid = bidsArray[i]; - let placementCode = ''; - let validSizes = []; - - bid.sizes = {height: bid.height, width: bid.width}; + // Include connection info if available + const CONNECTION = navigator.connection || navigator.webkitConnection; + if (CONNECTION && CONNECTION.type && CONNECTION.downlinkMax) { + data['netinfo.type'] = CONNECTION.type; + data['netinfo.downlinkMax'] = CONNECTION.downlinkMax; + } - var inBid = utils.getBidRequest(bid.callbackUid); + // Include debug data when available + if (!isInHostileIframe) { + const DEBUG_AD = (find(window.top.location.hash.split('&'), + val => includes(val, 'WS_DEBUG_FORCEADID') + ) || '').split('=')[1]; + data.forceAdId = DEBUG_AD; + } - if (inBid) { - bidCode = inBid.bidder; - placementCode = inBid.placementCode; - validSizes = inBid.sizes; + // Remove empty params + Object.keys(data).forEach((key) => { + if (data[key] === '' || data[key] === undefined) { + delete data[key]; + } + }); + + serverRequests.push({ + method: 'POST', + options: { + contentType: 'application/x-www-form-urlencoded' + }, + url: REQUEST_SERVER_URL, + data: parseQueryStringParameters(data) + }); + }); + preReqTime = Date.now(); + return serverRequests; + }, + + interpretResponse: function(serverResponse, request) { + const responseTime = Date.now() - preReqTime; + const successBids = serverResponse.body || []; + let bidResponses = []; + successBids.forEach((bid) => { + storeData({ + 'perf_status': 'OK', + 'perf_reqid': bid.reqId, + 'perf_ms': responseTime + }, `${LS_KEYS.PERF_DATA}${bid.reqId}`); + if (bid.status === 'ad') { + bidResponses.push({ + requestId: bid.bidId, + cpm: bid.cpm, + width: bid.width, + height: bid.height, + creativeId: bid.adId, + currency: bid.currency, + netRevenue: Boolean(bid.netRev), + ttl: bid.ttl, + referrer: getTopWindowReferrer(), + ad: bid.code + }); + } + }); + + return bidResponses + }, + + getUserSyncs: function(syncOptions, serverResponses = []) { + let userSyncs = []; + userSyncs = serverResponses.reduce((allSyncPixels, response) => { + if (response && response.body && response.body[0]) { + (response.body[0].syncPixels || []).forEach((url) => { + allSyncPixels.push({type: 'image', url}); + }); } + return allSyncPixels; + }, []); + return userSyncs; + } +}; + +function storeData(data, name, stringify = true) { + const value = stringify ? JSON.stringify(data) : data; + if (LOCAL_STORAGE_AVAILABLE) { + localStorage.setItem(name, value); + return true; + } else if (COOKIE_ENABLED) { + const theDate = new Date(); + const expDate = new Date(theDate.setMonth(theDate.getMonth() + 12)).toGMTString(); + window.document.cookie = `${name}=${value};path=/;expires=${expDate}`; + return true; + } +} - if (bid && bid.callbackUid && bid.status !== 'noad' && verifySize(bid.sizes, validSizes)) { - bidObject = bidfactory.createBid(1); - bidObject.bidderCode = bidCode; - bidObject.cpm = bid.cpm; - bidObject.cur = bid.currency; - bidObject.creative_id = bid.adId; - bidObject.ad = bid.code; - bidObject.width = bid.width; - bidObject.height = bid.height; - bidmanager.addBidResponse(placementCode, bidObject); - } else { - bidObject = bidfactory.createBid(2); - bidObject.bidderCode = bidCode; - bidmanager.addBidResponse(placementCode, bidObject); +function getData(name, remove = true) { + let data = []; + if (LOCAL_STORAGE_AVAILABLE) { + Object.keys(localStorage).filter((key) => { + if (key.indexOf(name) > -1) { + data.push(localStorage.getItem(key)); + if (remove) { + localStorage.removeItem(key); + } } - } + }); + } - function verifySize(bid, validSizes) { - for (var j = 0, k = validSizes.length; j < k; j++) { - if (bid.width === validSizes[j][0] && - bid.height === validSizes[j][1]) { - return true; + if (COOKIE_ENABLED) { + document.cookie.split(';').forEach((item) => { + let value = item.split('='); + if (value[0].indexOf(name) > -1) { + data.push(value[1]); + if (remove) { + document.cookie = `${value[0]}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`; } } - return false; - } - }; + }); + } + return data; +} - return { - callBids: _callBids +function pixelSyncPossibility() { + const userSync = config.getConfig('userSync'); + return userSync && userSync.pixelEnabled && userSync.syncEnabled ? userSync.syncsPerBidder : -1; +} + +function visibleOnLoad(element) { + if (element && element.getBoundingClientRect) { + const topPos = element.getBoundingClientRect().top; + return topPos < screen.height && topPos >= window.top.pageYOffset ? 1 : 0; }; + return ''; +} + +function getLcuid() { + let lcuid = getData(LS_KEYS.LC_UID, false)[0]; + if (!lcuid) { + const random = ('4' + new Date().getTime() + String(Math.floor(Math.random() * 1000000000))).substring(0, 18); + storeData(random, LS_KEYS.LC_UID, false); + lcuid = getData(LS_KEYS.LC_UID, false)[0]; + } + return lcuid; } -adaptermanager.registerBidAdapter(new WidespaceAdapter(), 'widespace'); +function encodedParamValue(value) { + const requiredStringify = typeof JSON.parse(JSON.stringify(value)) === 'object'; + return encodeURIComponent(requiredStringify ? JSON.stringify(value) : value); +} + +function getEngineUrl() { + const ENGINE_URL = 'https://engine.widespace.com/map/engine/dynadreq'; + return window.wisp && window.wisp.ENGINE_URL ? window.wisp.ENGINE_URL : ENGINE_URL; +} -module.exports = WidespaceAdapter; +registerBidder(spec); diff --git a/modules/widespaceBidAdapter.md b/modules/widespaceBidAdapter.md new file mode 100644 index 00000000000..1ca2b61d406 --- /dev/null +++ b/modules/widespaceBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + + +**Module Name:** Widespace Bidder Adapter. +**Module Type:** Bidder Adapter. +**Maintainer:** support@widespace.com + + +# Description + +Widespace Bidder Adapter for Prebid.js. +Banner and video formats are supported. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250], [300, 300]], + bids: [ + { + bidder: 'widespace', + params: { + sid: '7b6589bf-95c8-4656-90b9-af9737bb9ad3', // Required + currency: 'EUR', // Optional + bidfloor: '0.5', // Optional + demo: { // Optional + gender: 'M', + country: 'Sweden', + region: 'Stockholm', + postal: '15115', + city: 'Stockholm', + yob: '1984' + } + } + } + ] + } + ]; +``` diff --git a/modules/xendizBidAdapter.js b/modules/xendizBidAdapter.js new file mode 100644 index 00000000000..0f1c385a67c --- /dev/null +++ b/modules/xendizBidAdapter.js @@ -0,0 +1,102 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; + +const BIDDER_CODE = 'xendiz'; +const PREBID_ENDPOINT = 'prebid.xendiz.com'; +const SYNC_ENDPOINT = 'https://advsync.com/xendiz/ssp/?pixel=1'; + +const buildURI = () => { + return `//${PREBID_ENDPOINT}/request`; +} + +const getDevice = () => { + const lang = navigator.language || ''; + const width = window.screen.width; + const height = window.screen.height; + + return [lang, width, height]; +}; + +const buildItem = (req) => { + return [ + req.bidId, + req.params, + req.adUnitCode, + req.sizes.map(s => `${s[0]}x${s[1]}`) + ] +} + +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) { + return !!bid.params.pid; + }, + + /** + * 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) { + const payload = { + id: bidRequests[0].auctionId, + items: bidRequests.map(buildItem), + device: getDevice(), + page: utils.getTopWindowUrl(), + dt: +new Date() + }; + const payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: buildURI(), + 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) { + const bids = serverResponse.body.bids.map(bid => { + return { + requestId: bid.id, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + netRevenue: bid.netRevenue !== undefined ? bid.netRevenue : true, + dealId: bid.dealid, + currency: bid.cur || 'USD', + ttl: bid.exp || 900, + ad: bid.adm, + } + }); + + return bids; + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: SYNC_ENDPOINT + }]; + } + } +} + +registerBidder(spec); diff --git a/modules/xendizBidAdapter.md b/modules/xendizBidAdapter.md new file mode 100644 index 00000000000..4ecabe7070f --- /dev/null +++ b/modules/xendizBidAdapter.md @@ -0,0 +1,41 @@ +# Overview + +Module Name: Xendiz Bidder Adapter +Module Type: Bidder Adapter +Maintainer: hello@xendiz.com + +# Description + +Module that connects to Xendiz demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "xendiz", + params: { + pid: '00000000-0000-0000-0000-000000000000' + } + } + ] + },{ + code: 'test-div', + sizes: [[300, 50]], + bids: [ + { + bidder: "xendiz", + params: { + pid: '00000000-0000-0000-0000-000000000000', + ext: { + uid: '550e8400-e29b-41d4-a716-446655440000' + } + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/xhbBidAdapter.js b/modules/xhbBidAdapter.js deleted file mode 100644 index a45bb66bb52..00000000000 --- a/modules/xhbBidAdapter.js +++ /dev/null @@ -1,172 +0,0 @@ -import Adapter from 'src/adapter'; -import bidfactory from 'src/bidfactory'; -import bidmanager from 'src/bidmanager'; -import * as utils from 'src/utils'; -import { STATUS } from 'src/constants'; -import adaptermanager from 'src/adaptermanager'; -import { loadScript } from 'src/adloader'; - -const XhbAdapter = function XhbAdapter() { - const baseAdapter = new Adapter('xhb'); - let usersync = false; - - const _defaultBidderSettings = { - alwaysUseBid: true, - adserverTargeting: [ - { - key: 'hb_xhb_deal', - val: function (bidResponse) { - return bidResponse.dealId; - } - }, - { - key: 'hb_xhb_adid', - val: function (bidResponse) { - return bidResponse.adId; - } - }, - { - key: 'hb_xhb_size', - val: function (bidResponse) { - return bidResponse.width + 'x' + bidResponse.height; - } - } - ] - }; - bidmanager.registerDefaultBidderSetting('xhb', _defaultBidderSettings); - - baseAdapter.callBids = function (params) { - const anArr = params.bids; - for (let i = 0; i < anArr.length; i++) { - let bidRequest = anArr[i]; - let callbackId = bidRequest.bidId; - loadScript(buildJPTCall(bidRequest, callbackId)); - } - }; - - function buildJPTCall(bid, callbackId) { - // determine tag params - const placementId = utils.getBidIdParameter('placementId', bid.params); - const member = utils.getBidIdParameter('member', bid.params); - const inventoryCode = utils.getBidIdParameter('invCode', bid.params); - let referrer = utils.getBidIdParameter('referrer', bid.params); - const altReferrer = utils.getBidIdParameter('alt_referrer', bid.params); - - // Build tag, always use https - let jptCall = 'https://ib.adnxs.com/jpt?'; - - jptCall = utils.tryAppendQueryString(jptCall, 'callback', '$$PREBID_GLOBAL$$.handleXhbCB'); - jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); - jptCall = utils.tryAppendQueryString(jptCall, 'id', placementId); - jptCall = utils.tryAppendQueryString(jptCall, 'psa', '0'); - jptCall = utils.tryAppendQueryString(jptCall, 'member', member); - jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); - jptCall = utils.tryAppendQueryString(jptCall, 'traffic_source_code', (utils.getBidIdParameter('trafficSourceCode', bid.params))); - - // sizes takes a bit more logic - let sizeQueryString = ''; - let parsedSizes = utils.parseSizesInput(bid.sizes); - - // combine string into proper querystring for impbus - let parsedSizesLength = parsedSizes.length; - if (parsedSizesLength > 0) { - // first value should be "size" - sizeQueryString = 'size=' + parsedSizes[0]; - if (parsedSizesLength > 1) { - // any subsequent values should be "promo_sizes" - sizeQueryString += '&promo_sizes='; - for (let j = 1; j < parsedSizesLength; j++) { - sizeQueryString += parsedSizes[j] += ','; - } - // remove trailing comma - if (sizeQueryString && sizeQueryString.charAt(sizeQueryString.length - 1) === ',') { - sizeQueryString = sizeQueryString.slice(0, sizeQueryString.length - 1); - } - } - } - - if (sizeQueryString) { - jptCall += sizeQueryString + '&'; - } - - // append referrer - if (referrer === '') { - referrer = utils.getTopWindowUrl(); - } - - jptCall = utils.tryAppendQueryString(jptCall, 'referrer', referrer); - jptCall = utils.tryAppendQueryString(jptCall, 'alt_referrer', altReferrer); - - // remove the trailing "&" - if (jptCall.lastIndexOf('&') === jptCall.length - 1) { - jptCall = jptCall.substring(0, jptCall.length - 1); - } - - return jptCall; - } - - // expose the callback to the global object: - $$PREBID_GLOBAL$$.handleXhbCB = function (jptResponseObj) { - let bidCode; - - if (jptResponseObj && jptResponseObj.callback_uid) { - let responseCPM; - const id = jptResponseObj.callback_uid; - let placementCode = ''; - const bidObj = utils.getBidRequest(id); - if (bidObj) { - bidCode = bidObj.bidder; - placementCode = bidObj.placementCode; - - // set the status - bidObj.status = STATUS.GOOD; - } - - let bid = []; - if (jptResponseObj.result && jptResponseObj.result.ad && jptResponseObj.result.ad !== '') { - responseCPM = 0.00; - - // store bid response - // bid status is good (indicating 1) - let adId = jptResponseObj.result.creative_id; - bid = bidfactory.createBid(STATUS.GOOD, bidObj); - bid.creative_id = adId; - bid.bidderCode = bidCode; - bid.cpm = responseCPM; - bid.adUrl = jptResponseObj.result.ad; - bid.width = jptResponseObj.result.width; - bid.height = jptResponseObj.result.height; - bid.dealId = '99999999'; - - bidmanager.addBidResponse(placementCode, bid); - } else { - // no response data - // indicate that there is no bid for this placement - bid = bidfactory.createBid(STATUS.NO_BID, bidObj); - bid.bidderCode = bidCode; - bidmanager.addBidResponse(placementCode, bid); - } - - if (!usersync) { - let iframe = utils.createInvisibleIframe(); - iframe.src = '//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html'; - try { - document.body.appendChild(iframe); - } catch (error) { - utils.logError(error); - } - usersync = true; - } - } - }; - - return Object.assign(this, { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode, - buildJPTCall: buildJPTCall - }); -}; - -adaptermanager.registerBidAdapter(new XhbAdapter(), 'xhb'); - -module.exports = XhbAdapter; diff --git a/modules/yieldbotBidAdapter.js b/modules/yieldbotBidAdapter.js index 4f874a82502..43d94220907 100644 --- a/modules/yieldbotBidAdapter.js +++ b/modules/yieldbotBidAdapter.js @@ -1,200 +1,604 @@ -/** - * @overview Yieldbot sponsored Prebid.js adapter. - * @author elljoh - */ -var adloader = require('src/adloader'); -var bidfactory = require('src/bidfactory'); -var bidmanager = require('src/bidmanager'); -var utils = require('src/utils'); -var adaptermanager = require('src/adaptermanager'); +import * as utils from 'src/utils'; +import { formatQS as buildQueryString } from '../src/url'; +import { registerBidder } from 'src/adapters/bidderFactory'; /** - * Adapter for requesting bids from Yieldbot. - * - * @returns {Object} Object containing implementation for invocation in {@link module:adaptermanger.callBids} - * @class + * @module {BidderSpec} YieldbotBidAdapter + * @description Adapter for requesting bids from Yieldbot + * @see BidderSpec + * @author [elljoh]{@link https://github.com/elljoh} + * @private */ -function YieldbotAdapter() { - window.ybotq = window.ybotq || []; - - var ybotlib = { - BID_STATUS: { - PENDING: 0, - AVAILABLE: 1, - EMPTY: 2 - }, - pageLevelOption: false, - /** - * Builds the Yieldbot creative tag. - * - * @param {String} slot - The slot name to bid for - * @param {String} size - The dimenstions of the slot - * @private - */ - buildCreative: function (slot, size) { - return '' + - ''; - }, - /** - * Bid response builder. - * - * @param {Object} slotCriteria - Yieldbot bid criteria - * @private - */ - buildBid: function (slotCriteria) { - var bid = {}; +export const YieldbotAdapter = { + _adapterLoaded: Date.now(), + _navigationStart: 0, + _version: 'pbjs-yb-0.0.1', + _bidRequestCount: 0, + _pageviewDepth: 0, + _lastPageviewId: '', + _sessionBlocked: false, + _isInitialized: false, + _sessionTimeout: 180000, + _userTimeout: 2592000000, + _cookieLabels: ['n', 'u', 'si', 'pvd', 'lpv', 'lpvi', 'c'], - if (slotCriteria && slotCriteria.ybot_ad && slotCriteria.ybot_ad !== 'n') { - bid = bidfactory.createBid(ybotlib.BID_STATUS.AVAILABLE); + initialize: function() { + if (!this._isInitialized) { + this._pageviewDepth = this.pageviewDepth; + this._sessionBlocked = this.sessionBlocked; + this._isInitialized = true; + } + }, - bid.cpm = parseInt(slotCriteria.ybot_cpm) / 100.0 || 0; // Yieldbot CPM bids are in cents + /** + * Adapter version + * pbjs-yb-x.x.x + * @returns {string} The adapter version string + * @memberof module:YieldbotBidAdapter + */ + get version() { + return this._version; + }, - var szArr = slotCriteria.ybot_size ? slotCriteria.ybot_size.split('x') : [0, 0]; - var slot = slotCriteria.ybot_slot || ''; - var sizeStr = slotCriteria.ybot_size || ''; // Creative template needs the dimensions string + /** + * Is the user session blocked by the Yieldbot adserver.
+ * The Yieldbot adserver may return "block_session": true in a bid response. + * A session may be blocked for efficiency (i.e. Yieldbot has decided no to bid for the session), + * security and/or fraud detection. + * @returns {boolean} + * @readonly + * @memberof module:YieldbotBidAdapter + * @private + */ + get sessionBlocked() { + const cookieName = '__ybotn'; + const cookieValue = this.getCookie(cookieName); + const sessionBlocked = cookieValue ? parseInt(cookieValue, 10) || 0 : 0; + if (sessionBlocked) { + this.setCookie(cookieName, 1, this._sessionTimeout, '/'); + } + this._sessionBlocked = !!sessionBlocked; + return this._sessionBlocked; + }, - bid.width = szArr[0] || 0; - bid.height = szArr[1] || 0; + set sessionBlocked(blockSession) { + const cookieName = '__ybotn'; + const sessionBlocked = blockSession ? 1 : 0; + this.setCookie(cookieName, sessionBlocked, this._sessionTimeout, '/'); + }, - bid.ad = ybotlib.buildCreative(slot, sizeStr); + get userId() { + const cookieName = '__ybotu'; + let cookieValue = this.getCookie(cookieName); + if (!cookieValue) { + cookieValue = this.newId(); + this.setCookie(cookieName, cookieValue, this._userTimeout, '/'); + } + return cookieValue; + }, - // Add Yieldbot parameters to allow publisher bidderSettings.yieldbot specific targeting - for (var k in slotCriteria) { - bid[k] = slotCriteria[k]; - } - } else { - bid = bidfactory.createBid(ybotlib.BID_STATUS.EMPTY); - } + get sessionId() { + const cookieName = '__ybotsi'; + let cookieValue = this.getCookie(cookieName); + if (!cookieValue) { + cookieValue = this.newId(); + this.setCookie(cookieName, cookieValue, this._sessionTimeout, '/'); + } + return cookieValue; + }, - bid.bidderCode = 'yieldbot'; - return bid; - }, - /** - * Unique'ify slot sizes for a Yieldbot bid request
- * Bids may refer to a slot and dimension multiple times on a page, but should exist once in the request. - * @param {Array} sizes An array of sizes to deduplicate - * @private - */ - getUniqueSlotSizes: function(sizes) { - var newSizes = []; - var hasSize = {}; - if (utils.isArray(sizes)) { - for (var idx = 0; idx < sizes.length; idx++) { - var bidSize = sizes[idx] || ''; - if (bidSize && utils.isStr(bidSize) && !hasSize[bidSize]) { - var nSize = bidSize.split('x'); - if (nSize.length > 1) { - newSizes.push([nSize[0], nSize[1]]); - } - hasSize[bidSize] = true; + get pageviewDepth() { + const cookieName = '__ybotpvd'; + let cookieValue = parseInt(this.getCookie(cookieName), 10) || 0; + cookieValue++; + this.setCookie(cookieName, cookieValue, this._sessionTimeout, '/'); + return cookieValue; + }, + + get lastPageviewId() { + const cookieName = '__ybotlpvi'; + let cookieValue = this.getCookie(cookieName); + return cookieValue || ''; + }, + + set lastPageviewId(id) { + const cookieName = '__ybotlpvi'; + this.setCookie(cookieName, id, this._sessionTimeout, '/'); + }, + + get lastPageviewTime() { + const cookieName = '__ybotlpv'; + let cookieValue = this.getCookie(cookieName); + return cookieValue ? parseInt(cookieValue, 10) : 0; + }, + + set lastPageviewTime(ts) { + const cookieName = '__ybotlpv'; + this.setCookie(cookieName, ts, this._sessionTimeout, '/'); + }, + + /** + * Get/set the request base url used to form bid, ad markup and impression requests. + * @param {string} [prefix] the bidder request base url + * @returns {string} the request base Url string + * @memberof module:YieldbotBidAdapter + */ + urlPrefix: function(prefix) { + const cookieName = '__ybotc'; + const pIdx = prefix ? prefix.indexOf(':') : -1; + const url = pIdx !== -1 ? document.location.protocol + prefix.substr(pIdx + 1) : null; + let cookieValue = url || this.getCookie(cookieName); + if (!cookieValue) { + cookieValue = '//i.yldbt.com/m/'; + } + this.setCookie(cookieName, cookieValue, this._sessionTimeout, '/'); + return cookieValue; + }, + + /** + * Bidder identifier code. + * @type {string} + * @constant + * @memberof module:YieldbotBidAdapter + */ + get code() { return 'yieldbot'; }, + + /** + * A list of aliases which should also resolve to this bidder. + * @type {string[]} + * @constant + * @memberof module:YieldbotBidAdapter + */ + get aliases() { return []; }, + + /** + * @property {MediaType[]} [supportedMediaTypes]: A list of Media Types which the adapter supports. + * @constant + * @memberof module:YieldbotBidAdapter + */ + get supportedMediaTypes() { return ['banner']; }, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise. + * @memberof module:YieldbotBidAdapter + */ + isBidRequestValid: function(bid) { + let invalidSizeArray = false; + if (utils.isArray(bid.sizes)) { + const arr = bid.sizes.reduce((acc, cur) => acc.concat(cur), []).filter((item) => { + return !utils.isNumber(item); + }); + invalidSizeArray = arr.length !== 0; + } + const ret = bid && + bid.params && + utils.isStr(bid.params.psn) && + utils.isStr(bid.params.slot) && + !invalidSizeArray && + !!bid.params.psn && + !!bid.params.slot; + return ret; + }, + + /** + * 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. + * @memberof module:YieldbotBidAdapter + */ + buildRequests: function(bidRequests) { + const requests = []; + if (!this._optOut && !this.sessionBlocked) { + const searchParams = this.initBidRequestParams(); + this._bidRequestCount++; + + const pageviewIdToMap = searchParams['pvi']; + + const yieldbotSlotParams = this.getSlotRequestParams(pageviewIdToMap, bidRequests); + + searchParams['sn'] = yieldbotSlotParams['sn'] || ''; + searchParams['ssz'] = yieldbotSlotParams['ssz'] || ''; + + const bidUrl = this.urlPrefix() + yieldbotSlotParams.psn + '/v1/init'; + + searchParams['cts_ini'] = Date.now(); + requests.push({ + method: 'GET', + url: bidUrl, + data: searchParams, + yieldbotSlotParams: yieldbotSlotParams, + options: { + withCredentials: true, + customHeaders: { + Accept: 'application/json' } } - } - return newSizes; - }, - /** - * Yieldbot implementation of {@link module:adaptermanger.callBids} - * @param {Object} params - Adapter bid configuration object - * @private - */ - callBids: function (params) { - var bids = params.bids || []; - var ybotq = window.ybotq || []; - - ybotlib.pageLevelOption = false; - - ybotq.push(function () { - var yieldbot = window.yieldbot; - // Empty defined slot bids object - ybotlib.bids = {}; - ybotlib.parsedBidSizes = {}; - // Iterate through bids to obtain Yieldbot slot config - // - Slot config can be different between initial and refresh requests - var psn = 'ERROR_PREBID_DEFINE_YB_PSN'; - var slots = {}; - utils._each(bids, function (v) { - var bid = v; - // bidder params config: http://prebid.org/dev-docs/bidders/yieldbot.html - // - last psn wins - psn = bid.params && bid.params.psn ? bid.params.psn : psn; - var slotName = bid.params && bid.params.slot ? bid.params.slot : 'ERROR_PREBID_DEFINE_YB_SLOT'; - var parsedSizes = utils.parseSizesInput(bid.sizes) || []; - slots[slotName] = slots[slotName] || []; - slots[slotName] = slots[slotName].concat(parsedSizes); - ybotlib.bids[bid.bidId] = bid; - ybotlib.parsedBidSizes[bid.bidId] = parsedSizes; + }); + } + return requests; + }, + + /** + * 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. + * @memberof module:YieldbotBidAdapter + */ + getUserSyncs: function(syncOptions, serverResponses) { + const userSyncs = []; + if (syncOptions.pixelEnabled && + serverResponses.length > 0 && + serverResponses[0].body && + serverResponses[0].body.user_syncs && + utils.isArray(serverResponses[0].body.user_syncs)) { + const responseUserSyncs = serverResponses[0].body.user_syncs; + responseUserSyncs.forEach((pixel) => { + userSyncs.push({ + type: 'image', + url: pixel }); + }); + } + return userSyncs; + }, - for (var bidSlots in slots) { - if (slots.hasOwnProperty(bidSlots)) { - // The same slot name and size may be used for multiple bids. Get unique sizes - // for the request. - slots[bidSlots] = ybotlib.getUniqueSlotSizes(slots[bidSlots]); - } - } + /** + * @typeDef {YieldbotBid} YieldbotBid + * @type {object} + * @property {string} slot Yieldbot config param slot + * @property {string} cpm Yieldbot bid quantity/label + * @property {string} size Slot dimensions of the form <width>x<height> + * @memberof module:YieldbotBidAdapter + */ + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest Request object submitted which produced the response. + * @return {Bid[]} An array of bids which were nested inside the server. + * @memberof module:YieldbotBidAdapter + */ + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const responseBody = serverResponse && serverResponse.body ? serverResponse.body : {}; + this._optOut = responseBody.optout || false; + if (this._optOut) { + this.clearAllCookies(); + } + if (!this._optOut && !this._sessionBlocked) { + const slotBids = responseBody.slots && responseBody.slots.length > 0 ? responseBody.slots : []; + slotBids.forEach((bid) => { + if (bid.slot && bid.size && bid.cpm) { + const sizeParts = bid.size ? bid.size.split('x') : [1, 1]; + const width = sizeParts[0] || 1; + const height = sizeParts[1] || 1; + const cpm = parseInt(bid.cpm, 10) / 100.0 || 0; - if (yieldbot._initialized !== true) { - yieldbot.pub(psn); - for (var slotName in slots) { - if (slots.hasOwnProperty(slotName)) { - yieldbot.defineSlot(slotName, { sizes: slots[slotName] }); - } - } - yieldbot.enableAsync(); - yieldbot.go(); - } else if (!utils.isEmpty(slots)) { - yieldbot.nextPageview(slots); + const yieldbotSlotParams = bidRequest.yieldbotSlotParams || null; + const ybBidId = bidRequest.data['pvi']; + const paramKey = `${ybBidId}:${bid.slot}:${bid.size || ''}`; + const bidIdMap = yieldbotSlotParams && yieldbotSlotParams.bidIdMap ? yieldbotSlotParams.bidIdMap : {}; + const requestId = bidIdMap[paramKey] || ''; + + const urlPrefix = this.urlPrefix(responseBody.url_prefix); + const tagObject = this.buildAdCreativeTag(urlPrefix, bid, bidRequest); + const bidResponse = { + requestId: requestId, + cpm: cpm, + width: width, + height: height, + creativeId: tagObject.creativeId, + currency: 'USD', + netRevenue: true, + ttl: this._sessionTimeout / 1000, // [s] + ad: tagObject.ad + }; + bidResponses.push(bidResponse); } }); + } + return bidResponses; + }, - ybotq.push(function () { - ybotlib.handleUpdateState(); - }); - adloader.loadScript('//cdn.yldbt.com/js/yieldbot.intent.js', null, true); - }, - /** - * Yieldbot bid request callback handler. - * - * @see {@link YieldbotAdapter~_callBids} - * @private + /** + * Initializes search parameters common to both ad request and impression Urls. + * @param {string} adRequestId Yieldbot ad request identifier + * @param {BidRequest} bidRequest The request that is the source of the impression + * @returns {object} Search parameter key/value pairs + * @memberof module:YieldbotBidAdapter + */ + initAdRequestParams: function(adRequestId, bidRequest) { + let commonSearchParams = {}; + commonSearchParams['v'] = this._version; + commonSearchParams['vi'] = bidRequest.data.vi || this._version + '-vi'; + commonSearchParams['si'] = bidRequest.data.si || this._version + '-si'; + commonSearchParams['pvi'] = bidRequest.data.pvi || this._version + '-pvi'; + commonSearchParams['ri'] = adRequestId; + return commonSearchParams; + }, + + buildAdUrl: function(urlPrefix, publisherNumber, commonSearchParams, bid) { + const searchParams = Object.assign({}, commonSearchParams); + searchParams['cts_res'] = Date.now(); + searchParams['slot'] = bid.slot + ':' + bid.size; + searchParams['ioa'] = this.intersectionObserverAvailable(window); + + const queryString = buildQueryString(searchParams) || ''; + const adUrl = urlPrefix + + publisherNumber + + '/ad/creative.js?' + + queryString; + return adUrl; + }, + + buildImpressionUrl: function(urlPrefix, publisherNumber, commonSearchParams) { + const searchParams = Object.assign({}, commonSearchParams); + const queryString = buildQueryString(searchParams) || ''; + const impressionUrl = urlPrefix + + publisherNumber + + '/ad/impression.gif?' + + queryString; + return impressionUrl; + }, + + /** + * Object with Yieldbot ad markup representation and unique creative identifier. + * @typeDef {TagObject} TagObject + * @type {object} + * @property {string} creativeId bidder specific creative identifier for tracking at the source + * @property {string} ad ad creative markup + * @memberof module:YieldbotBidAdapter + */ + /** + * Builds the ad creative markup. + * @param {string} urlPrefix base url for Yieldbot requests + * @param {module:YieldbotBidAdapter.YieldbotBid} bid Bidder slot bid object + * @returns {module:YieldbotBidAdapter.TagObject} + * @memberof module:YieldbotBidAdapter + */ + buildAdCreativeTag: function(urlPrefix, bid, bidRequest) { + const ybotAdRequestId = this.newId(); + const commonSearchParams = this.initAdRequestParams(ybotAdRequestId, bidRequest); + const publisherNumber = bidRequest && bidRequest.yieldbotSlotParams ? bidRequest.yieldbotSlotParams.psn || '' : ''; + const adUrl = this.buildAdUrl(urlPrefix, publisherNumber, commonSearchParams, bid); + const impressionUrl = this.buildImpressionUrl(urlPrefix, publisherNumber, commonSearchParams); + + const htmlMarkup = `
`; + return { ad: htmlMarkup, creativeId: ybotAdRequestId }; + }, + + intersectionObserverAvailable: function (win) { + /* Ref: + * https://github.com/w3c/IntersectionObserver/blob/gh-pages/polyfill/intersection-observer.js#L23-L25 */ - handleUpdateState: function () { - var yieldbot = window.yieldbot; - var slotUsed = {}; - - for (var bidId in ybotlib.bids) { - if (ybotlib.bids.hasOwnProperty(bidId)) { - var bidRequest = ybotlib.bids[bidId] || null; - - if (bidRequest && bidRequest.params && bidRequest.params.slot) { - var placementCode = bidRequest.placementCode || 'ERROR_YB_NO_PLACEMENT'; - var criteria = yieldbot.getSlotCriteria(bidRequest.params.slot); - var requestedSizes = ybotlib.parsedBidSizes[bidId] || []; - - var slotSizeOk = false; - for (var idx = 0; idx < requestedSizes.length; idx++) { - var requestedSize = requestedSizes[idx]; - - if (!slotUsed[criteria.ybot_slot] && requestedSize === criteria.ybot_size) { - slotSizeOk = true; - slotUsed[criteria.ybot_slot] = true; - break; - } - } - var bid = ybotlib.buildBid(slotSizeOk ? criteria : { ybot_ad: 'n' }); - bidmanager.addBidResponse(placementCode, bid); + return win && + win.IntersectionObserver && + win.IntersectionObserverEntry && + win.IntersectionObserverEntry.prototype && + 'intersectionRatio' in win.IntersectionObserverEntry.prototype; + }, + + /** + * @typeDef {BidParams} BidParams + * @property {string} psn Yieldbot publisher customer number + * @property {object} searchParams bid request Url search parameters + * @property {object} searchParams.sn slot names + * @property {object} searchParams.szz slot sizes + * @memberof module:YieldbotBidAdapter + * @private + */ + /** + * Builds the common Yieldbot bid request Url query parameters.
+ * Slot bid related parameters are handled separately. The so-called common parameters + * here are request identifiers, page properties and user-agent attributes. + * @returns {object} query parameter key/value items + * @memberof module:YieldbotBidAdapter + */ + initBidRequestParams: function() { + const params = {}; + + params['cts_js'] = this._adapterLoaded; + params['cts_ns'] = this._navigationStart; + params['v'] = this._version; + + const userId = this.userId; + const sessionId = this.sessionId; + const pageviewId = this.newId(); + const currentBidTime = Date.now(); + const lastBidTime = this.lastPageviewTime; + const lastBidId = this.lastPageviewId; + this.lastPageviewTime = currentBidTime; + this.lastPageviewId = pageviewId; + params['vi'] = userId; + params['si'] = sessionId; + params['pvd'] = this._pageviewDepth; + params['pvi'] = pageviewId; + params['lpv'] = lastBidTime; + params['lpvi'] = lastBidId; + params['bt'] = this._bidRequestCount === 0 ? 'init' : 'refresh'; + + params['ua'] = navigator.userAgent; + params['np'] = navigator.platform; + params['la'] = + navigator.browserLanguage ? navigator.browserLanguage : navigator.language; + params['to'] = + (new Date()).getTimezoneOffset() / 60; + params['sd'] = + window.screen.width + 'x' + window.screen.height; + + params['lo'] = utils.getTopWindowUrl(); + params['r'] = utils.getTopWindowReferrer(); + + params['e'] = ''; + + return params; + }, + + /** + * Bid mapping key to the Prebid internal bidRequestId
+ * Format {pageview id}:{slot name}:{width}x{height} + * @typeDef {BidRequestKey} BidRequestKey + * @type {string} + * @example "jbgxsxqxyxvqm2oud7:leaderboard:728x90" + * @memberof module:YieldbotBidAdapter + */ + + /** + * Internal Yieldbot adapter bid and ad markup request state + *
+ * When interpreting a server response, the associated requestId is lookeded up + * in this map when creating a {@link Bid} response object. + * @typeDef {BidRequestMapping} BidRequestMapping + * @type {object} + * @property {Object.} {*} Yieldbot bid to requestId mapping item + * @memberof module:YieldbotBidAdapter + * @example + * { + * "jbgxsxqxyxvqm2oud7:leaderboard:728x90": "2b7e31676ce17", + * "jbgxsxqxyxvqm2oud7:medrec:300x250": "2b7e31676cd89", + * "jcrvvd6eoileb8w8ko:medrec:300x250": "2b7e316788ca7" + * } + * @memberof module:YieldbotBidAdapter + */ + + /** + * Rationalized set of Yieldbot bids for adUnits and mapping to respective Prebid.js bidId. + * @typeDef {BidSlots} BidSlots + * @property {string} psn Yieldbot publisher site identifier taken from bidder params + * @property {string} sn slot names + * @property {string} szz slot sizes + * @property {module:YieldbotBidAdapter.BidRequestMapping} bidIdMap Yieldbot bid to Prebid bidId mapping + * @memberof module:YieldbotBidAdapter + */ + + /** + * Gets unique slot name and sizes for query parameters object + * @param {string} pageviewId The Yieldbot bid request identifier + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server + * @returns {module:YieldbotBidAdapter.BidSlots} Yieldbot specific bid parameters and bid identifier mapping + * @memberof module:YieldbotBidAdapter + */ + getSlotRequestParams: function(pageviewId, bidRequests) { + const params = {}; + const bidIdMap = {}; + bidRequests = bidRequests || []; + pageviewId = pageviewId || ''; + try { + const slotBids = {}; + bidRequests.forEach((bid) => { + params.psn = params.psn || bid.params.psn || ''; + utils.parseSizesInput(bid.sizes).forEach(sz => { + const slotName = bid.params.slot; + if (sz && (!slotBids[slotName] || !slotBids[slotName].some(existingSize => existingSize === sz))) { + slotBids[slotName] = slotBids[slotName] || []; + slotBids[slotName].push(sz); + const paramKey = pageviewId + ':' + slotName + ':' + sz; + bidIdMap[paramKey] = bid.bidId; } - } + }); + }); + + const nm = []; + const sz = []; + for (let idx in slotBids) { + const slotName = idx; + const slotSizes = slotBids[idx]; + nm.push(slotName); + sz.push(slotSizes.join('.')); + } + params['sn'] = nm.join('|'); + params['ssz'] = sz.join('|'); + + params.bidIdMap = bidIdMap; + } catch (err) {} + return params; + }, + + getCookie: function(name) { + const cookies = document.cookie.split(';'); + let value = null; + for (let idx = 0; idx < cookies.length; idx++) { + const item = cookies[idx].split('='); + const itemName = item[0].replace(/^\s+|\s+$/g, ''); + if (itemName == name) { + value = item.length > 1 ? decodeURIComponent(item[1].replace(/^\s+|\s+$/g, '')) : null; + break; } } - }; - return { - callBids: ybotlib.callBids, - getUniqueSlotSizes: ybotlib.getUniqueSlotSizes - }; -} + return value; + }, + + setCookie: function(name, value, expireMillis, path, domain, secure) { + const dataValue = encodeURIComponent(value); + const cookieStr = name + '=' + dataValue + + (expireMillis ? ';expires=' + new Date(Date.now() + expireMillis).toGMTString() : '') + + (path ? ';path=' + path : '') + + (domain ? ';domain=' + domain : '') + + (secure ? ';secure' : ''); + + document.cookie = cookieStr; + }, + + deleteCookie: function(name, path, domain, secure) { + return this.setCookie(name, '', -1, path, domain, secure); + }, + + /** + * Clear all first-party cookies. + */ + clearAllCookies: function() { + const labels = this._cookieLabels; + for (let idx = 0; idx < labels.length; idx++) { + const label = '__ybot' + labels[idx]; + this.deleteCookie(label); + } + }, + + /** + * Generate a new Yieldbot format id
+ * Base 36 and lowercase: <[ms] since epoch><[base36]{10}> + * @example "jbgxsyrlx9fxnr1hbl" + * @private + */ + newId: function() { + return (+new Date()).toString(36) + 'xxxxxxxxxx' + .replace(/[x]/g, function() { + return (0 | Math.random() * 36).toString(36); + }); + }, + + /** + * Create a delegate function with 'this' context of the YieldbotAdapter object. + * @param {object} instance Object for 'this' context in function apply + * @param {function} fn Function to execute in instance context + * @returns {function} the provided function bound to the instance context + * @memberof module:YieldbotBidAdapter + */ + createDelegate: function(instance, fn) { + var outerArgs = Array.prototype.slice.call(arguments, 2); + return function() { + return fn.apply(instance, outerArgs.length > 0 ? Array.prototype.slice.call(arguments, 0).concat(outerArgs) : arguments); + }; + } +}; + +YieldbotAdapter.initialize(); -adaptermanager.registerBidAdapter(new YieldbotAdapter(), 'yieldbot'); +export const spec = { + code: YieldbotAdapter.code, + aliases: YieldbotAdapter.aliases, + supportedMediaTypes: YieldbotAdapter.supportedMediaTypes, + isBidRequestValid: YieldbotAdapter.createDelegate(YieldbotAdapter, YieldbotAdapter.isBidRequestValid), + buildRequests: YieldbotAdapter.createDelegate(YieldbotAdapter, YieldbotAdapter.buildRequests), + interpretResponse: YieldbotAdapter.createDelegate(YieldbotAdapter, YieldbotAdapter.interpretResponse), + getUserSyncs: YieldbotAdapter.createDelegate(YieldbotAdapter, YieldbotAdapter.getUserSyncs) +}; -module.exports = YieldbotAdapter; +YieldbotAdapter._navigationStart = Date.now(); +registerBidder(spec); diff --git a/modules/yieldbotBidAdapter.md b/modules/yieldbotBidAdapter.md new file mode 100644 index 00000000000..db6f4dc100b --- /dev/null +++ b/modules/yieldbotBidAdapter.md @@ -0,0 +1,192 @@ +# Overview + +``` +Module Name: Yieldbot Bid Adapter +Module Type: Bidder Adapter +Maintainer: pubops@yieldbot.com +``` + +# Description +The Yieldbot Prebid.js bid adapter integrates Yieldbot demand to publisher inventory. + +# BaseAdapter Settings + +| Setting | Value | +| :-------------------- | :------------ | +| `supportedMediaTypes` | **banner** | +| `getUserSyncs` | **image** pixel | +| `ttl` | **180** [s] | +| `currency` | **USD** | + +# Parameters +The following table lists parameters required for Yieldbot bidder configuration. +See also [Test Parameters](#test-parameters) for an illustration of parameter usage. + +| Name | Scope | Description | Example | +| :------ | :------- | :------------------------------------------------------------------ | :-------------- | +| `psn` | required | The Yieldbot publisher account short name identifier | "7b25" | +| `slot` | required | The Yieldbot slot name associated to the publisher adUnit to bid on | "mobile_REC_2" | + +## Example Bidder Configuration +```javascript +var adUnit0 = { + code: '/00000000/leaderboard', + mediaTypes: { + banner: { + sizes: [728, 90] + } + }, + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '7b25', + slot: 'desktop_LB' + } + } + ] +}; +``` + +# Test Parameters +For integration testing, the Yieldbot Platform can be set to always return a bid for requested slots. + +When Yieldbot testing mode is enabled, a cookie (`__ybot_test`) on the domain `.yldbt.com` tells the Yieldbot ad server to always return a bid. Each bid is associated to a static mock integration testing creative. + +- **Enable** integration testing mode: + - http://i.yldbt.com/integration/start +- **Disable** integration testing mode: + - http://i.yldbt.com/integration/stop + +***Note:*** + +- No ad serving metrics are impacted when integration testing mode is enabled. +- The `__ybot_test` cookie expires in 24 hours. +- It is good practice to click "Stop testing" when testing is complete, to return to normal ad delivery. + +For reference, the test bidder configuration below is included in the following manual test/example file [test/spec/e2e/gpt-examples/gpt_yieldbot.html](../test/spec/e2e/gpt-examples/gpt_yieldbot.html) +- Replace the adUnit `code` values with your respective DFP adUnitCode. +- ***Remember*** to **Enable** Yieldbot testing mode to force a bid to be returned. + +```javascript +var adUnit0 = { + code: '/00000000/leaderboard', + mediaTypes: { + banner: { + sizes: [728, 90] + } + }, + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'leaderboard' + } + } + ] +}; + +var adUnit1 = { + code: '/00000000/medium-rectangle', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'medrec' + } + } + ] +}; + +var adUnit2 = { + code: '/00000000/large-rectangle', + mediaTypes: { + banner: { + sizes: [[300, 600]] + } + }, + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'sidebar' + } + } + ] +}; + +var adUnit3 = { + code: '/00000000/skyscraper', + mediaTypes: { + banner: { + sizes: [[160, 600]] + } + }, + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'skyscraper' + } + } + ] +}; +``` + +# Yieldbot Query Parameters + +| Name | Description | +| :--- | :---------- | +| `apie` | Yieldbot error description parameter | +| `bt` | Yieldbot bid request type: `initial` or `refresh` | +| `cts_ad` | Yieldbot ad creative request sent timestamp, in milliseconds since the UNIX epoch | +| `cts_imp` | Yieldbot ad impression request sent timestamp, in milliseconds since the UNIX epoch | +| `cts_ini` | Yieldbot bid request sent timestamp, in milliseconds since the UNIX epoch | +| `cts_js` | Adapter code interpreting started timestamp, in milliseconds since the UNIX epoch | +| `cts_ns` | Performance timing navigationStart | +| `cts_rend` | Yieldbot ad creative render started timestamp, in milliseconds since the UNIX epoch | +| `cts_res` | Yieldbot bid response processing started timestamp, in milliseconds since the UNIX epoch | +| `e` | Yieldbot search parameters terminator | +| `ioa` | Indicator that the user-agent supports the Intersection Observer API | +| `it` | Indicator to specify Yieldbot creative rendering occured in an iframe: same/cross origin (`so`)/(`co`) or top (`none`) | +| `la` | Language and locale of the user-agent | +| `lo` | The page visit location Url | +| `lpv` | Time in milliseconds since the last page visit | +| `lpvi` | Pageview identifier for the last pageview within the session TTL | +| `np` | User-agent browsing platform | +| `pvd` | Counter for page visits within a session | +| `pvi` | Page visit identifier | +| `r` | The referring page Url | +| `ri` | Yieldbot ad request identifier | +| `sb` | Yieldbot ads blocked by user opt-out or suspicious activity detected during session | +| `sd` | User-agent screen dimensions | +| `si` | Publisher site visit session identifier | +| `slot` | Slot name for Yieldbot ad markup request e.g. `:x` | +| `sn` | Yieldbot bid slot names | +| `ssz` | Dimensions for the respective bid slot names | +| `to` | Number of hours offset from UTC | +| `ua` | User-Agent string | +| `v` | The version of the YieldbotAdapter | +| `vi` | First party user identifier | + + +# First-party Cookies + +| Name | Description | +| :--- | :---------- | +| `__ybotn` | The session is temporarily suspended from the ad server e.g. User-Agent, Geo location or suspicious activity | +| `__ybotu` | The Yieldbot first-party user identifier | +| `__ybotsi` | The user session identifier | +| `__ybotpvd` | The session pageview depth | +| `__ybotlpvi` | The last pageview identifier within the session | +| `__ybotlpv` | The time in **[ms]** since the last visit within the session | +| `__ybotc` | Geo/IP proximity location request Url | diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js new file mode 100644 index 00000000000..dde5c70077e --- /dev/null +++ b/modules/yieldlabBidAdapter.js @@ -0,0 +1,107 @@ +import * as utils from 'src/utils' +import { registerBidder } from 'src/adapters/bidderFactory' +import find from 'core-js/library/fn/array/find' +import { VIDEO, BANNER } from 'src/mediaTypes' + +const ENDPOINT = 'https://ad.yieldlab.net' +const BIDDER_CODE = 'yieldlab' +const BID_RESPONSE_TTL_SEC = 300 +const CURRENCY_CODE = 'EUR' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO, BANNER], + + isBidRequestValid: function (bid) { + if (bid && bid.params && bid.params.adslotId && bid.params.adSize) { + return true + } + return false + }, + + /** + * This method should build correct URL + * @param validBidRequests + * @returns {{method: string, url: string}} + */ + buildRequests: function (validBidRequests) { + const adslotIds = [] + const timestamp = Date.now() + + utils._each(validBidRequests, function (bid) { + adslotIds.push(bid.params.adslotId) + }) + + const adslots = adslotIds.join(',') + + return { + method: 'GET', + url: `${ENDPOINT}/yp/${adslots}?ts=${timestamp}&json=true`, + validBidRequests: validBidRequests + } + }, + + /** + * Map ad values and pricing and stuff + * @param serverResponse + * @param originalBidRequest + */ + interpretResponse: function (serverResponse, originalBidRequest) { + const bidResponses = [] + const timestamp = Date.now() + + originalBidRequest.validBidRequests.forEach(function (bidRequest) { + if (!serverResponse.body) { + return + } + + let matchedBid = find(serverResponse.body, function (bidResponse) { + return bidRequest.params.adslotId == bidResponse.id + }) + + if (matchedBid) { + const sizes = parseSize(bidRequest.params.adSize) + const bidResponse = { + requestId: bidRequest.bidId, + cpm: matchedBid.price / 100, + width: sizes[0], + height: sizes[1], + creativeId: '' + matchedBid.id, + dealId: matchedBid.pid, + currency: CURRENCY_CODE, + netRevenue: false, + ttl: BID_RESPONSE_TTL_SEC, + referrer: '', + ad: `` + } + if (isVideo(bidRequest)) { + bidResponse.mediaType = VIDEO + bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/${sizes[0]}x${sizes[1]}?ts=${timestamp}` + } + + bidResponses.push(bidResponse) + } + }) + return bidResponses + } +}; + +/** + * Is this a video format? + * @param {String} format + * @returns {Boolean} + */ +function isVideo (format) { + return utils.deepAccess(format, 'mediaTypes.video') +} + +/** + * Expands a 'WxH' string as a 2-element [W, H] array + * @param {String} size + * @returns {Array} + */ +function parseSize (size) { + return size.split('x').map(Number) +} + +registerBidder(spec) diff --git a/modules/yieldlabBidAdapter.md b/modules/yieldlabBidAdapter.md new file mode 100644 index 00000000000..0b11794ac8f --- /dev/null +++ b/modules/yieldlabBidAdapter.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: Yieldlab Bidder Adapter +Module Type: Bidder Adapter +Maintainer: solutions@yieldlab.de +``` + +# Description + +Module that connects to Yieldlab's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: "banner", + sizes: [[728, 90]], + bids: [{ + bidder: "yieldlab", + params: { + adslotId: "5220336", + supplyId: "1381604", + adSize: "728x90" + } + }] + }, { + code: "video", + sizes: [[640, 480]], + mediaTypes: { + video: { + context: "instream" + } + }, + bids: [{ + bidder: "yieldlab", + params: { + adslotId: "5220339", + supplyId: "1381604", + adSize: "640x480" + } + }] + } + ]; +``` diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index d311bb5722c..ad2aff7dec8 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -1,151 +1,304 @@ -var utils = require('src/utils.js'); -var adloader = require('src/adloader.js'); -var bidmanager = require('src/bidmanager.js'); -var bidfactory = require('src/bidfactory.js'); -var adaptermanager = require('src/adaptermanager'); +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; -/** - * Adapter for requesting bids from Yieldmo. - * - * @returns {{callBids: _callBids}} - * @constructor - */ - -var YieldmoAdapter = function YieldmoAdapter() { - function _callBids(params) { - var bids = params.bids; - adloader.loadScript(buildYieldmoCall(bids)); - } - - function buildYieldmoCall(bids) { - // build our base tag, based on if we are http or https - var ymURI = '//bid.yieldmo.com/exchange/prebid?'; - var ymCall = document.location.protocol + ymURI; - - // Placement specific information - ymCall = _appendPlacementInformation(ymCall, bids); - - // General impression params - ymCall = _appendImpressionInformation(ymCall); +const BIDDER_CODE = 'yieldmo'; +const CURRENCY = 'USD'; +const TIME_TO_LIVE = 300; +const NET_REVENUE = true; +const SYNC_ENDPOINT = 'https://static.yieldmo.com/blank.min.html?orig='; +const SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; +const localWindow = getTopWindow(); - // remove the trailing "&" - if (ymCall.lastIndexOf('&') === ymCall.length - 1) { - ymCall = ymCall.substring(0, ymCall.length - 1); +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 && bid.adUnitCode && bid.bidId); + }, + /** + * 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) { + let serverRequest = { + p: [], + page_url: utils.getTopWindowUrl(), + bust: new Date().getTime().toString(), + pr: utils.getTopWindowReferrer(), + scrd: localWindow.devicePixelRatio || 0, + dnt: getDNT(), + e: getEnvironment(), + description: getPageDescription(), + title: localWindow.document.title || '', + w: localWindow.innerWidth, + h: localWindow.innerHeight + }; + bidRequests.forEach((request) => { + serverRequest.p.push(addPlacement(request)); + }); + serverRequest.p = '[' + serverRequest.p.toString() + ']'; + return { + method: 'GET', + url: SERVER_ENDPOINT, + data: serverRequest + } + }, + /** + * Makes Yieldmo 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) { + let bids = []; + let data = serverResponse.body; + if (data.length > 0) { + data.forEach((response) => { + if (response.cpm && response.cpm > 0) { + bids.push(createNewBid(response)); + } + }); + } + return bids; + }, + getUserSync: function(syncOptions) { + if (trackingEnabled(syncOptions)) { + return [{ + type: 'iframe', + url: SYNC_ENDPOINT + utils.getOrigin() + }]; + } else { + return []; } + } +} +registerBidder(spec); - utils.logMessage('ymCall request built: ' + ymCall); +/*************************************** + * Helper Functions + ***************************************/ - return ymCall; +/** + * Adds placement information to array + * @param request bid request + */ +function addPlacement(request) { + const placementInfo = { + placement_id: request.adUnitCode, + callback_id: request.bidId, + sizes: request.sizes } + if (request.params && request.params.placementId) { + placementInfo.ym_placement_id = request.params.placementId + } + return JSON.stringify(placementInfo); +} - function _appendPlacementInformation(url, bids) { - var placements = []; - var placement; - var bid; - - for (var i = 0; i < bids.length; i++) { - bid = bids[i]; +/** + * creates a new bid with response information + * @param response server response + */ +function createNewBid(response) { + return { + requestId: response['callback_id'], + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creativeId, + currency: CURRENCY, + netRevenue: NET_REVENUE, + ttl: TIME_TO_LIVE, + ad: response.ad + }; +} - placement = {}; - placement.callback_id = bid.bidId; - placement.placement_id = bid.placementCode; - placement.sizes = bid.sizes; +/** + * Detects if tracking is allowed + * @returns false if dnt or if not iframe/pixel enabled + */ +function trackingEnabled(options) { + return (isIOS() && !getDNT() && options.iframeEnabled); +} - if (bid.params && bid.params.placementId) { - placement.ym_placement_id = bid.params.placementId; - } +/** + * Detects whether we're in iOS + * @returns true if in iOS + */ +function isIOS() { + return /iPhone|iPad|iPod/i.test(window.navigator.userAgent); +} - placements.push(placement); - } +/** + * Detects whether dnt is true + * @returns true if user enabled dnt + */ +function getDNT() { + return window.doNotTrack === '1' || window.navigator.doNotTrack === '1' || false; +} - url = utils.tryAppendQueryString(url, 'p', JSON.stringify(placements)); - return url; +/** + * 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 _appendImpressionInformation(url) { - var page_url = document.location; // page url - var pr = document.referrer || ''; // page's referrer - var dnt = (navigator.doNotTrack || false).toString(); // true if user enabled dnt (false by default) - var _s = document.location.protocol === 'https:' ? 1 : 0; // 1 if page is secure - var description = _getPageDescription(); - var title = document.title || ''; // Value of the title from the publisher's page. - var bust = new Date().getTime().toString(); // cache buster - var scrd = window.devicePixelRatio || 0; // screen pixel density - - url = utils.tryAppendQueryString(url, 'callback', '$$PREBID_GLOBAL$$.YMCB'); - url = utils.tryAppendQueryString(url, 'page_url', page_url); - url = utils.tryAppendQueryString(url, 'pr', pr); - url = utils.tryAppendQueryString(url, 'bust', bust); - url = utils.tryAppendQueryString(url, '_s', _s); - url = utils.tryAppendQueryString(url, 'scrd', scrd); - url = utils.tryAppendQueryString(url, 'dnt', dnt); - url = utils.tryAppendQueryString(url, 'description', description); - url = utils.tryAppendQueryString(url, 'title', title); - - return url; +function getTopWindow() { + try { + return window.top; + } catch (e) { + return window; } +} - 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 ''; - } +/*************************************** + * Detect Environment Helper Functions + ***************************************/ + +/** + * Represents a method for loading Yieldmo ads. Environments affect + * which formats can be loaded into the page + * Environments: + * CodeOnPage: 0, // div directly on publisher's page + * Amp: 1, // google Accelerate Mobile Pages ampproject.org + * Mraid = 2, // native loaded through the MRAID spec, without Yieldmo's SDK https://www.iab.net/media/file/IAB_MRAID_v2_FINAL.pdf + * Dfp: 4, // google doubleclick for publishers https://www.doubleclickbygoogle.com/ + * DfpInAmp: 5, // AMP page containing a DFP iframe + * SafeFrame: 10, + * DfpSafeFrame: 11,Sandboxed: 16, // An iframe that can't get to the top window. + * SuperSandboxed: 89, // An iframe without allow-same-origin + * Unknown: 90, // A default sandboxed implementation delivered by EnvironmentDispatch when all positive environment checks fail + */ + +/** + * Detects what environment we're in + * @returns Environment kind + */ +function getEnvironment() { + if (isSuperSandboxedIframe()) { + return 89; + } else if (isDfpInAmp()) { + return 5; + } else if (isDfp()) { + return 4; + } else if (isAmp()) { + return 1; + } else if (isDFPSafeFrame()) { + return 11; + } else if (isSafeFrame()) { + return 10; + } else if (isMraid()) { + return 2; + } else if (isCodeOnPage()) { + return 0; + } else if (isSandboxedIframe()) { + return 16; + } else { + return 90; } +} - // expose the callback to the global object: - $$PREBID_GLOBAL$$.YMCB = function(ymResponses) { - if (ymResponses && ymResponses.constructor === Array && ymResponses.length > 0) { - for (var i = 0; i < ymResponses.length; i++) { - _registerPlacementBid(ymResponses[i]); - } - } else { - // If an incorrect response is returned, register error bids for all placements - // to prevent Prebid waiting till timeout for response - _registerNoResponseBids(); +/** + * @returns true if we are running on the top window at dispatch time + */ +function isCodeOnPage() { + return window === window.parent; +} - utils.logMessage('No prebid response for placement %%PLACEMENT%%'); +/** + * @returns true if the environment is both DFP and AMP + */ +function isDfpInAmp() { + return isDfp() && isAmp(); +} + +/** + * @returns true if the window is in an iframe whose id and parent element id match DFP + */ +function isDfp() { + try { + const frameElement = window.frameElement; + const parentElement = window.frameElement.parentNode; + if (frameElement && parentElement) { + return frameElement.id.indexOf('google_ads_iframe') > -1 && parentElement.id.indexOf('google_ads_iframe') > -1; } - }; + return false; + } catch (e) { + return false; + } +} - function _registerPlacementBid(response) { - var bidObj = utils.getBidRequest(response.callback_id); - var placementCode = bidObj && bidObj.placementCode; - var bid = []; - - if (response && response.cpm && response.cpm !== 0) { - bid = bidfactory.createBid(1, bidObj); - bid.bidderCode = 'yieldmo'; - bid.cpm = response.cpm; - bid.ad = response.ad; - bid.width = response.width; - bid.height = response.height; - bidmanager.addBidResponse(placementCode, bid); - } else { - // no response data - if (bidObj) { utils.logMessage('No prebid response from yieldmo for placementCode: ' + bidObj.placementCode); } - bid = bidfactory.createBid(2, bidObj); - bid.bidderCode = 'yieldmo'; - bidmanager.addBidResponse(placementCode, bid); +/** +* @returns true if there is an AMP context object +*/ +function isAmp() { + try { + const ampContext = window.context || window.parent.context; + if (ampContext && ampContext.pageViewId) { + return ampContext; } + return false; + } catch (e) { + return false; } +} - function _registerNoResponseBids() { - var yieldmoBidRequests = $$PREBID_GLOBAL$$._bidsRequested.find(bid => bid.bidderCode === 'yieldmo'); +/** + * @returns true if the environment is a SafeFrame. + */ +function isSafeFrame() { + return window.$sf && window.$sf.ext; +} - utils._each(yieldmoBidRequests.bids, function (currentBid) { - var bid = []; - bid = bidfactory.createBid(2, currentBid); - bid.bidderCode = 'yieldmo'; - bidmanager.addBidResponse(currentBid.placementCode, bid); - }); +/** + * @returns true if the environment is a dfp safe frame. + */ +function isDFPSafeFrame() { + if (window.location && window.location.href) { + const href = window.location.href; + return isSafeFrame() && href.indexOf('google') !== -1 && href.indexOf('safeframe') !== -1; } + return false; +} - return Object.assign(this, { - callBids: _callBids - }); -}; +/** + * Return true if we are in an iframe and can't access the top window. + */ +function isSandboxedIframe() { + return window.top !== window && !window.frameElement; +} -adaptermanager.registerBidAdapter(new YieldmoAdapter(), 'yieldmo'); +/** + * Return true if we cannot document.write to a child iframe (this implies no allow-same-origin) + */ +function isSuperSandboxedIframe() { + const sacrificialIframe = window.document.createElement('iframe'); + try { + sacrificialIframe.setAttribute('style', 'display:none'); + window.document.body.appendChild(sacrificialIframe); + sacrificialIframe.contentWindow._testVar = true; + window.document.body.removeChild(sacrificialIframe); + return false; + } catch (e) { + window.document.body.removeChild(sacrificialIframe); + return true; + } +} -module.exports = YieldmoAdapter; +/** + * @returns true if the window has the attribute identifying MRAID + */ +function isMraid() { + return !!(window.mraid); +} diff --git a/modules/yieldmoBidAdapter.md b/modules/yieldmoBidAdapter.md new file mode 100644 index 00000000000..8c60202d7ea --- /dev/null +++ b/modules/yieldmoBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Yieldmo Bid Adapter +Module Type: Bidder Adapter +Maintainer: opensource@yieldmo.com +Note: Our ads will only render in mobile +``` + +# Description + +Connects to Yieldmo Ad Server for bids. + +Yieldmo bid adapter supports Banner. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'yieldmo', + params: { + placementId: '1779781193098233305' // string with at most 19 characters (may include numbers only) + } + }] + } +]; +``` \ No newline at end of file diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js new file mode 100644 index 00000000000..9f94b5b815e --- /dev/null +++ b/modules/yieldoneBidAdapter.js @@ -0,0 +1,71 @@ +import * as utils from 'src/utils'; +import {config} from 'src/config'; +import {registerBidder} from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'yieldone'; +const ENDPOINT_URL = '//y.one.impact-ad.jp/h_bid'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['y1'], + 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' + }; + return { + method: 'GET', + url: ENDPOINT_URL, + data: payload, + } + }); + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + const crid = response.crid || 0; + const width = response.width || 0; + const height = response.height || 0; + const cpm = response.cpm * 1000 || 0; + if (width !== 0 && height !== 0 && cpm !== 0 && crid !== 0) { + const dealId = response.dealid || ''; + const currency = response.currency || 'JPY'; + 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.adTag + }; + bidResponses.push(bidResponse); + } + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/yieldoneBidAdapter.md b/modules/yieldoneBidAdapter.md new file mode 100644 index 00000000000..b5d96f822b5 --- /dev/null +++ b/modules/yieldoneBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +``` +Module Name: YIELDONE Bidder Adapter +Module Type: Bidder Adapter +Maintainer: y1dev@platform-one.co.jp +``` + +# Description + +Connect to YIELDONE for bids. + +THE YIELDONE adapter requires setup and approval from the YIELDONE team. Please reach out to your account team or y1s@platform-one.co.jp for more information. + +# Test Parameters +``` + var adUnits = [{ + code: 'banner-ad-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'yieldone', + params: { + placementId: '44082' + } + }] + }]; +``` diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js new file mode 100644 index 00000000000..2801ec3afb8 --- /dev/null +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -0,0 +1,144 @@ +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'; +const yuktamediaAnalyticsVersion = 'v1.0.0'; + +let initOptions; +let auctionTimestamp; +let events = { + bids: [] +}; + +var yuktamediaAnalyticsAdapter = Object.assign(adapter( + { + emptyUrl, + analyticsType + }), { + track({ eventType, args }) { + if (typeof args !== 'undefined') { + if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { + args.forEach(item => { mapBidResponse(item, 'timeout'); }); + } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { + events.auctionInit = args; + auctionTimestamp = args.timestamp; + } else if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) { + mapBidRequests(args).forEach(item => { events.bids.push(item) }); + } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { + mapBidResponse(args, 'response'); + } else if (eventType === CONSTANTS.EVENTS.BID_WON) { + send({ + bidWon: mapBidResponse(args, 'win') + }, 'won'); + } + } + + if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + send(events, 'auctionEnd'); + } + } +}); + +function mapBidRequests(params) { + let arr = []; + if (typeof params.bids !== 'undefined' && params.bids.length) { + params.bids.forEach(function (bid) { + arr.push({ + bidderCode: bid.bidder, + bidId: bid.bidId, + adUnitCode: bid.adUnitCode, + requestId: bid.bidderRequestId, + auctionId: bid.auctionId, + transactionId: bid.transactionId, + sizes: utils.parseSizesInput(bid.sizes).toString(), + renderStatus: 1, + requestTimestamp: params.auctionStart + }); + }); + } + return arr; +} + +function mapBidResponse(bidResponse, status) { + if (status !== 'win') { + let bid = events.bids.filter(o => o.bidId == bidResponse.bidId || o.bidId == bidResponse.requestId)[0]; + Object.assign(bid, { + bidderCode: bidResponse.bidder, + bidId: status == 'timeout' ? bidResponse.bidId : bidResponse.requestId, + adUnitCode: bidResponse.adUnitCode, + auctionId: bidResponse.auctionId, + creativeId: bidResponse.creativeId, + transactionId: bidResponse.transactionId, + currency: bidResponse.currency, + cpm: bidResponse.cpm, + netRevenue: bidResponse.netRevenue, + mediaType: bidResponse.mediaType, + statusMessage: bidResponse.statusMessage, + status: bidResponse.status, + renderStatus: status == 'timeout' ? 3 : 2, + timeToRespond: bidResponse.timeToRespond, + requestTimestamp: bidResponse.requestTimestamp, + responseTimestamp: bidResponse.responseTimestamp + }); + } else if (status == 'win') { + return { + bidderCode: bidResponse.bidder, + bidId: bidResponse.requestId, + adUnitCode: bidResponse.adUnitCode, + auctionId: bidResponse.auctionId, + creativeId: bidResponse.creativeId, + transactionId: bidResponse.transactionId, + currency: bidResponse.currency, + cpm: bidResponse.cpm, + netRevenue: bidResponse.netRevenue, + renderedSize: bidResponse.size, + mediaType: bidResponse.mediaType, + statusMessage: bidResponse.statusMessage, + status: bidResponse.status, + renderStatus: 4, + timeToRespond: bidResponse.timeToRespond, + requestTimestamp: bidResponse.requestTimestamp, + responseTimestamp: bidResponse.responseTimestamp + } + } +} + +function send(data, status) { + let location = utils.getTopWindowLocation(); + let secure = location.protocol == 'https:'; + if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { + data.auctionInit = Object.assign({ host: location.host, path: location.pathname, hash: location.hash, search: location.search }, data.auctionInit); + } + data.initOptions = initOptions; + + let yuktamediaAnalyticsRequestUrl = url.format({ + protocol: secure ? 'https' : 'http', + hostname: 'analytics-prebid.yuktamedia.com', + pathname: status == 'auctionEnd' ? '/api/bids' : '/api/bid/won', + search: { + auctionTimestamp: auctionTimestamp, + yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion, + prebidVersion: $$PREBID_GLOBAL$$.version + } + }); + + ajax(yuktamediaAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'application/json' }); +} + +yuktamediaAnalyticsAdapter.originEnableAnalytics = yuktamediaAnalyticsAdapter.enableAnalytics; +yuktamediaAnalyticsAdapter.enableAnalytics = function (config) { + initOptions = config.options; + yuktamediaAnalyticsAdapter.originEnableAnalytics(config); +}; + +adaptermanager.registerAnalyticsAdapter({ + adapter: yuktamediaAnalyticsAdapter, + code: 'yuktamedia' +}); + +export default yuktamediaAnalyticsAdapter; diff --git a/modules/yuktamediaAnalyticsAdapter.md b/modules/yuktamediaAnalyticsAdapter.md new file mode 100644 index 00000000000..a21675b6b1d --- /dev/null +++ b/modules/yuktamediaAnalyticsAdapter.md @@ -0,0 +1,22 @@ +# Overview +Module Name: YuktaMedia Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: info@yuktamedia.com + +# Description + +Analytics adapter for prebid provided by YuktaMedia. Contact info@yuktamedia.com for information. + +# Test Parameters + +``` +{ + provider: 'yuktamedia', + options : { + pubId : 50357 //id provided by YuktaMedia LLP + pubKey: 'xxx' //key provided by YuktaMedia LLP + } +} +``` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000..8509196c7ba --- /dev/null +++ b/package-lock.json @@ -0,0 +1,16812 @@ +{ + "name": "prebid.js", + "version": "1.10.0-pre", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@gulp-sourcemaps/identity-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.1.tgz", + "integrity": "sha1-z6I7xYQPkQTOMqZedNt+epdLvuE=", + "requires": { + "acorn": "5.5.3", + "css": "2.2.1", + "normalize-path": "2.1.1", + "source-map": "0.5.7", + "through2": "2.0.3" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "requires": { + "normalize-path": "2.1.1", + "through2": "2.0.3" + } + }, + "@sinonjs/formatio": { + "version": "2.0.0", + "resolved": "http://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", + "dev": true, + "requires": { + "samsam": "1.3.0" + } + }, + "JSONStream": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", + "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", + "dev": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accepts": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz", + "integrity": "sha1-5fHzkoxtlf2WVYw27D2dDeSm7Oo=", + "dev": true, + "requires": { + "mime-types": "2.1.18", + "negotiator": "0.5.3" + } + }, + "acorn": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", + "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==" + }, + "acorn-dynamic-import": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "dev": true, + "requires": { + "acorn": "4.0.13" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + } + } + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "addressparser": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", + "integrity": "sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y=", + "dev": true, + "optional": true + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "agent-base": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", + "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=", + "dev": true, + "requires": { + "extend": "3.0.1", + "semver": "5.0.3" + }, + "dependencies": { + "semver": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", + "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=", + "dev": true + } + } + }, + "ajv": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.2.0.tgz", + "integrity": "sha1-r6wpW7qgFSRJ5SJ0LkVHwa6TKNI=", + "dev": true, + "requires": { + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "amqplib": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.2.tgz", + "integrity": "sha512-l9mCs6LbydtHqRniRwYkKdqxVa6XMz3Vw1fh+2gJaaVgTM6Jk3o8RccAKWKtlhT1US5sWrFh+KKxsVUALURSIA==", + "dev": true, + "optional": true, + "requires": { + "bitsyntax": "0.0.4", + "bluebird": "3.5.1", + "buffer-more-ints": "0.0.2", + "readable-stream": "1.1.14", + "safe-buffer": "5.1.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true, + "optional": true + } + } + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "3.1.10", + "normalize-path": "2.1.1" + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "1.0.0" + } + }, + "append-transform": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", + "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", + "dev": true, + "requires": { + "default-require-extensions": "1.0.0" + } + }, + "archiver": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-0.14.4.tgz", + "integrity": "sha1-W53bn17hzu8hy487Ag5iQOy0MVw=", + "dev": true, + "requires": { + "async": "0.9.2", + "buffer-crc32": "0.2.13", + "glob": "4.3.5", + "lazystream": "0.1.0", + "lodash": "3.2.0", + "readable-stream": "1.0.34", + "tar-stream": "1.1.5", + "zip-stream": "0.5.2" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "glob": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.3.5.tgz", + "integrity": "sha1-gPuwjKVA8jiszl0R0em8QedRc9M=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "2.0.10", + "once": "1.4.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "lazystream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-0.1.0.tgz", + "integrity": "sha1-GyXWPHcqTCDwpe0KnXf0hLbhaSA=", + "dev": true, + "requires": { + "readable-stream": "1.0.34" + } + }, + "lodash": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.2.0.tgz", + "integrity": "sha1-S/UKMkP5rrC6xBpV09WZBnWkYvs=", + "dev": true + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-iterate": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-1.1.2.tgz", + "integrity": "sha512-1hWSHTIlG/8wtYD+PPX5AOBtKWngpDFjrsrHgZpe+JdgNGz0udYu6ZIkAa/xuenIUEqFv7DvE2Yr60jxweJSrQ==", + "dev": true + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "array.from": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/array.from/-/array.from-0.2.0.tgz", + "integrity": "sha1-LGJ7G3bf8t7yNl+gUrZcPVheX2s=", + "dev": true + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + } + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "ast-types": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.3.tgz", + "integrity": "sha512-XA5o5dsNw8MhyW0Q7MWXJWc4oOzZKbdsEJq45h7c8q/d9DwWZ5F2ugUc1PuMLPGsUnphCt/cNDHu8JeBbxf1qA==", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/atob/-/atob-1.1.3.tgz", + "integrity": "sha1-lfE2KbEsOlGl0hWr3OKqnzL4B3M=" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true + }, + "aws4": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", + "dev": true + }, + "axios": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.15.3.tgz", + "integrity": "sha1-LJ1jiy4ZGgjqHWzJiOrda6W9wFM=", + "dev": true, + "optional": true, + "requires": { + "follow-redirects": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "follow-redirects": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz", + "integrity": "sha1-jjQpjL0uF28lTv/sdaHHjMhJ/Tc=", + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.9" + } + } + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-core": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.22.0.tgz", + "integrity": "sha1-ZD3q61ILzSsGwR45lFyHfgIA0Sg=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.1", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.5.1", + "debug": "2.6.9", + "json5": "0.5.1", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.8", + "slash": "1.0.0", + "source-map": "0.5.7" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.5", + "source-map": "0.5.7", + "trim-right": "1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-helper-bindify-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", + "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true, + "requires": { + "babel-helper-explode-assignable-expression": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-builder-react-jsx": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", + "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "esutils": "2.0.2" + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.5" + } + }, + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-explode-class": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", + "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", + "dev": true, + "requires": { + "babel-helper-bindify-decorators": "6.24.1", + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "dev": true, + "requires": { + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.5" + } + }, + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-loader": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.4.tgz", + "integrity": "sha512-/hbyEvPzBJuGpk9o80R0ZyTej6heEOr59GoEUtn8qFKbnx4cJm9FWES6J/iv644sYgrtVw9JJQkjaLW/bqb5gw==", + "dev": true, + "requires": { + "find-cache-dir": "1.0.0", + "loader-utils": "1.1.0", + "mkdirp": "0.5.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-syntax-async-generators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", + "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", + "dev": true + }, + "babel-plugin-syntax-class-constructor-call": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz", + "integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=", + "dev": true + }, + "babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", + "dev": true + }, + "babel-plugin-syntax-decorators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", + "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", + "dev": true + }, + "babel-plugin-syntax-do-expressions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz", + "integrity": "sha1-V0d1YTmqJtOQ0JQQsDdEugfkeW0=", + "dev": true + }, + "babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", + "dev": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, + "babel-plugin-syntax-export-extensions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz", + "integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=", + "dev": true + }, + "babel-plugin-syntax-flow": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", + "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", + "dev": true + }, + "babel-plugin-syntax-function-bind": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz", + "integrity": "sha1-SMSV8Xe98xqYHnMvVa3AvdJgH0Y=", + "dev": true + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", + "dev": true + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "dev": true + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-system-import-transformer": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-system-import-transformer/-/babel-plugin-system-import-transformer-3.1.0.tgz", + "integrity": "sha1-038Mro5h7zkGAggzHZMbXmMNfF8=", + "dev": true, + "requires": { + "babel-plugin-syntax-dynamic-import": "6.18.0" + } + }, + "babel-plugin-transform-async-generator-functions": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", + "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-generators": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-class-constructor-call": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz", + "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", + "dev": true, + "requires": { + "babel-plugin-syntax-class-constructor-call": "6.18.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", + "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", + "dev": true, + "requires": { + "babel-helper-explode-class": "6.24.1", + "babel-plugin-syntax-decorators": "6.13.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-decorators-legacy": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.4.tgz", + "integrity": "sha1-dBtY9sW86eYCfgiC2cmU8E82aSU=", + "dev": true, + "requires": { + "babel-plugin-syntax-decorators": "6.13.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-do-expressions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz", + "integrity": "sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs=", + "dev": true, + "requires": { + "babel-plugin-syntax-do-expressions": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.5" + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "dev": true, + "requires": { + "babel-helper-define-map": "6.26.0", + "babel-helper-function-name": "6.24.1", + "babel-helper-optimise-call-expression": "6.24.1", + "babel-helper-replace-supers": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", + "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "requires": { + "babel-helper-replace-supers": "6.24.1", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true, + "requires": { + "babel-helper-call-delegate": "6.24.1", + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "requires": { + "babel-helper-regex": "6.26.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "requires": { + "babel-helper-regex": "6.26.0", + "babel-runtime": "6.26.0", + "regexpu-core": "2.0.0" + } + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", + "babel-plugin-syntax-exponentiation-operator": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-export-extensions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz", + "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", + "dev": true, + "requires": { + "babel-plugin-syntax-export-extensions": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-flow-strip-types": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", + "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", + "dev": true, + "requires": { + "babel-plugin-syntax-flow": "6.18.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-function-bind": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz", + "integrity": "sha1-xvuOlqwpajELjPjqQBRiQH3fapc=", + "dev": true, + "requires": { + "babel-plugin-syntax-function-bind": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-object-assign": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.22.0.tgz", + "integrity": "sha1-+Z0vZvGgsNSY40bFNZaEdAyqILo=", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-object-rest-spread": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", + "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", + "dev": true, + "requires": { + "babel-plugin-syntax-object-rest-spread": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-react-display-name": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", + "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-react-jsx": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", + "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", + "dev": true, + "requires": { + "babel-helper-builder-react-jsx": "6.26.0", + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-react-jsx-self": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", + "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", + "dev": true, + "requires": { + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-react-jsx-source": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", + "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", + "dev": true, + "requires": { + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "requires": { + "regenerator-transform": "0.10.1" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-preset-env": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", + "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-async-to-generator": "6.24.1", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.26.0", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", + "babel-plugin-transform-es2015-modules-umd": "6.24.1", + "babel-plugin-transform-es2015-object-super": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "6.24.1", + "babel-plugin-transform-exponentiation-operator": "6.24.1", + "babel-plugin-transform-regenerator": "6.26.0", + "browserslist": "2.11.3", + "invariant": "2.2.4", + "semver": "5.5.0" + } + }, + "babel-preset-flow": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", + "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", + "dev": true, + "requires": { + "babel-plugin-transform-flow-strip-types": "6.22.0" + } + }, + "babel-preset-react": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", + "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", + "dev": true, + "requires": { + "babel-plugin-syntax-jsx": "6.18.0", + "babel-plugin-transform-react-display-name": "6.25.0", + "babel-plugin-transform-react-jsx": "6.24.1", + "babel-plugin-transform-react-jsx-self": "6.22.0", + "babel-plugin-transform-react-jsx-source": "6.22.0", + "babel-preset-flow": "6.23.0" + } + }, + "babel-preset-stage-0": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz", + "integrity": "sha1-VkLRUEL5E4TX5a+LyIsduVsDnmo=", + "dev": true, + "requires": { + "babel-plugin-transform-do-expressions": "6.22.0", + "babel-plugin-transform-function-bind": "6.22.0", + "babel-preset-stage-1": "6.24.1" + } + }, + "babel-preset-stage-1": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz", + "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", + "dev": true, + "requires": { + "babel-plugin-transform-class-constructor-call": "6.24.1", + "babel-plugin-transform-export-extensions": "6.22.0", + "babel-preset-stage-2": "6.24.1" + } + }, + "babel-preset-stage-2": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", + "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", + "dev": true, + "requires": { + "babel-plugin-syntax-dynamic-import": "6.18.0", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-decorators": "6.24.1", + "babel-preset-stage-3": "6.24.1" + } + }, + "babel-preset-stage-3": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", + "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "dev": true, + "requires": { + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-async-generator-functions": "6.24.1", + "babel-plugin-transform-async-to-generator": "6.24.1", + "babel-plugin-transform-exponentiation-operator": "6.24.1", + "babel-plugin-transform-object-rest-spread": "6.26.0" + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "6.26.0", + "babel-runtime": "6.26.0", + "core-js": "2.5.5", + "home-or-tmp": "2.0.0", + "lodash": "4.17.5", + "mkdirp": "0.5.1", + "source-map-support": "0.4.18" + }, + "dependencies": { + "babel-core": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", + "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.1", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.5.1", + "debug": "2.6.9", + "json5": "0.5.1", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.8", + "slash": "1.0.0", + "source-map": "0.5.7" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "2.5.5", + "regenerator-runtime": "0.11.1" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.5" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.4", + "lodash": "4.17.5" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.5", + "to-fast-properties": "1.0.3" + } + }, + "babelify": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/babelify/-/babelify-8.0.0.tgz", + "integrity": "sha512-xVr63fKEvMWUrrIbqlHYsMcc5Zdw4FSVesAHgkgajyCE1W8gbm9rbMakqavhxKvikGYMhEcqxTwB/gQmQ6lBtw==", + "dev": true + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "bail": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", + "integrity": "sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + } + } + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "base64-url": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz", + "integrity": "sha1-GZ/WYXAqDnt9yubgaYuwicUvbXg=", + "dev": true + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "dev": true + }, + "basic-auth": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", + "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=", + "dev": true + }, + "basic-auth-connect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz", + "integrity": "sha1-/bC0OWLKe0BFanwrtI/hc9otISI=", + "dev": true + }, + "batch": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.5.3.tgz", + "integrity": "sha1-PzQU84AyF0O/wQQvmoP/HVgk1GQ=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "requires": { + "callsite": "1.0.0" + } + }, + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "dev": true, + "requires": { + "buffers": "0.1.1", + "chainsaw": "0.1.0" + } + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true + }, + "binaryextensions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-1.0.1.tgz", + "integrity": "sha1-HmN0iLNbWL2l9HdL+WpSEqjJB1U=", + "dev": true + }, + "bitsyntax": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz", + "integrity": "sha1-6xDMb4K4xJDj6FaY8H6D1G4MuoI=", + "dev": true, + "optional": true, + "requires": { + "buffer-more-ints": "0.0.2" + } + }, + "bl": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", + "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=", + "dev": true, + "requires": { + "readable-stream": "1.0.34" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "blob": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", + "dev": true + }, + "block-loader": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-loader/-/block-loader-2.1.0.tgz", + "integrity": "sha1-u7OYrVqEPGxx95opb0tt9LAlcxI=", + "dev": true + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", + "dev": true, + "requires": { + "continuable-cache": "0.3.1", + "error": "7.0.2", + "raw-body": "1.1.7", + "safe-json-parse": "1.0.1" + } + }, + "body-parser": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.13.3.tgz", + "integrity": "sha1-wIzzMMM1jhUQFqBXRvE/ApyX+pc=", + "dev": true, + "requires": { + "bytes": "2.1.0", + "content-type": "1.0.4", + "debug": "2.2.0", + "depd": "1.0.1", + "http-errors": "1.3.1", + "iconv-lite": "0.4.11", + "on-finished": "2.3.0", + "qs": "4.0.0", + "raw-body": "2.1.7", + "type-is": "1.6.16" + }, + "dependencies": { + "bytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz", + "integrity": "sha1-rJPEEOL/ycx89LRks4KJBn9eR7Q=", + "dev": true + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "qs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", + "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=", + "dev": true + }, + "raw-body": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "dev": true, + "requires": { + "bytes": "2.4.0", + "iconv-lite": "0.4.13", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", + "dev": true + } + } + } + } + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-resolve": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", + "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "1.0.3", + "cipher-base": "1.0.4", + "create-hash": "1.2.0", + "evp_bytestokey": "1.0.3", + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "1.2.0", + "browserify-des": "1.0.1", + "evp_bytestokey": "1.0.3" + } + }, + "browserify-des": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.1.tgz", + "integrity": "sha512-zy0Cobe3hhgpiOM32Tj7KQ3Vl91m0njwsjzZQK1L+JDf11dzP9qIvjreVinsvXrgfjhStXwUWAEpB9D7Gwmayw==", + "dev": true, + "requires": { + "cipher-base": "1.0.4", + "des.js": "1.0.0", + "inherits": "2.0.3" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "randombytes": "2.0.6" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "elliptic": "6.4.0", + "inherits": "2.0.3", + "parse-asn1": "5.1.1" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "1.0.6" + } + }, + "browserslist": { + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "dev": true, + "requires": { + "caniuse-lite": "1.0.30000830", + "electron-to-chromium": "1.3.42" + } + }, + "browserstack": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.0.tgz", + "integrity": "sha1-tWVCWtYu1ywQgqHrl51TE8fUdU8=", + "dev": true, + "requires": { + "https-proxy-agent": "1.0.0" + } + }, + "browserstacktunnel-wrapper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.2.tgz", + "integrity": "sha512-7w7HYA00qjBtuQH0c5rqW7RbWPHyRROqTZofwNp5G0sKc2fYChsTfbHz3ul8Yd+ffkQvR81m+iPjEB004P6kxQ==", + "dev": true, + "requires": { + "https-proxy-agent": "1.0.0", + "unzip": "0.1.11" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "1.3.0", + "ieee754": "1.1.11", + "isarray": "1.0.0" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "buffer-from": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", + "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", + "dev": true + }, + "buffer-more-ints": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz", + "integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw=", + "dev": true + }, + "buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", + "dev": true + }, + "buildmail": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/buildmail/-/buildmail-4.0.1.tgz", + "integrity": "sha1-h393OLeHKYccmhBeO4N9K+EaenI=", + "dev": true, + "optional": true, + "requires": { + "addressparser": "1.0.1", + "libbase64": "0.1.0", + "libmime": "3.0.0", + "libqp": "1.1.0", + "nodemailer-fetch": "1.6.0", + "nodemailer-shared": "1.1.0", + "punycode": "1.4.1" + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "2.1.1", + "map-obj": "1.0.1" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "caniuse-lite": { + "version": "1.0.30000830", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000830.tgz", + "integrity": "sha512-yMqGkujkoOIZfvOYiWdqPALgY/PVGiqCHUJb6yNq7xhI/pR+gQO0U2K6lRDqAiJv4+CIU3CtTLblNGw0QGnr6g==", + "dev": true + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true + }, + "ccount": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.3.tgz", + "integrity": "sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw==", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true, + "requires": { + "assertion-error": "1.1.0", + "deep-eql": "0.1.3", + "type-detect": "1.0.0" + } + }, + "chai-nightwatch": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/chai-nightwatch/-/chai-nightwatch-0.1.1.tgz", + "integrity": "sha1-HKVt52jTwIaP5/wvTTLC/olOa+k=", + "dev": true, + "requires": { + "assertion-error": "1.0.0", + "deep-eql": "0.1.3" + }, + "dependencies": { + "assertion-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", + "dev": true + } + } + }, + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "dev": true, + "requires": { + "traverse": "0.3.9" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "character-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.2.tgz", + "integrity": "sha512-sMoHX6/nBiy3KKfC78dnEalnpn0Az0oSNvqUWYTtYrhRI5iUIYsROU48G+E+kMFQzqXaJ8kHJZ85n7y6/PHgwQ==", + "dev": true + }, + "character-entities-html4": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.2.tgz", + "integrity": "sha512-sIrXwyna2+5b0eB9W149izTPJk/KkJTg6mEzDGibwBUkyH1SbDa+nf515Ppdi3MaH35lW0JFJDWeq9Luzes1Iw==", + "dev": true + }, + "character-entities-legacy": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz", + "integrity": "sha512-9NB2VbXtXYWdXzqrvAHykE/f0QJxzaKIpZ5QzNZrrgQ7Iyxr2vnfS8fCBNVW9nUEZE0lo57nxKRqnzY/dKrwlA==", + "dev": true + }, + "character-reference-invalid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz", + "integrity": "sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ==", + "dev": true + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "chokidar": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", + "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", + "dev": true, + "requires": { + "anymatch": "2.0.0", + "async-each": "1.0.1", + "braces": "2.3.2", + "fsevents": "1.2.2", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.0", + "normalize-path": "2.1.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0", + "upath": "1.0.4" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", + "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "process-nextick-args": "2.0.0", + "readable-stream": "2.3.6" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collapse-white-space": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.4.tgz", + "integrity": "sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw==", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "1.0.0", + "object-visit": "1.0.1" + } + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colors": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.1.tgz", + "integrity": "sha512-s8+wktIuDSLffCywiwSxQOMqtPxML11a/dtHE17tMn4B1MSWw/C22EKf7M2KGUBcDaVFEGT+S8N02geDXeuNKg==", + "dev": true + }, + "combine-lists": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", + "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "comma-separated-tokens": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.5.tgz", + "integrity": "sha512-Cg90/fcK93n0ecgYTAz1jaA3zvnQ0ExlmKY1rdbyHqAx6BHxwoJc+J7HDu0iuQ7ixEs1qaa+WyQ6oeuBpYP1iA==", + "dev": true, + "requires": { + "trim": "0.0.1" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compare-versions": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.1.0.tgz", + "integrity": "sha512-4hAxDSBypT/yp2ySFD346So6Ragw5xmBn/e/agIGl3bZr6DLUqnoRZPusxKrXdYRZpgexO9daejmIenlq/wrIQ==", + "dev": true + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "compress-commons": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-0.2.9.tgz", + "integrity": "sha1-Qi2SdDDAGr0GzUVbbfwEy0z4ADw=", + "dev": true, + "requires": { + "buffer-crc32": "0.2.13", + "crc32-stream": "0.3.4", + "node-int64": "0.3.3", + "readable-stream": "1.0.34" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "compressible": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.13.tgz", + "integrity": "sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k=", + "dev": true, + "requires": { + "mime-db": "1.33.0" + } + }, + "compression": { + "version": "1.5.2", + "resolved": "http://registry.npmjs.org/compression/-/compression-1.5.2.tgz", + "integrity": "sha1-sDuNhub4rSloPLqN+R3cb/x3s5U=", + "dev": true, + "requires": { + "accepts": "1.2.13", + "bytes": "2.1.0", + "compressible": "2.0.13", + "debug": "2.2.0", + "on-headers": "1.0.1", + "vary": "1.0.1" + }, + "dependencies": { + "bytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz", + "integrity": "sha1-rJPEEOL/ycx89LRks4KJBn9eR7Q=", + "dev": true + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "1.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" + } + }, + "concat-with-sourcemaps": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.0.5.tgz", + "integrity": "sha512-YtnS0VEY+e2Khzsey/6mra9EoM6h/5gxaC0e3mcHpA5yfDxafhygytNmcJWodvUgyXzSiL5MSkPO6bQGgfliHw==", + "dev": true, + "requires": { + "source-map": "0.6.1" + } + }, + "connect": { + "version": "2.30.2", + "resolved": "https://registry.npmjs.org/connect/-/connect-2.30.2.tgz", + "integrity": "sha1-jam8vooFTT0xjXTf7JA7XDmhtgk=", + "dev": true, + "requires": { + "basic-auth-connect": "1.0.0", + "body-parser": "1.13.3", + "bytes": "2.1.0", + "compression": "1.5.2", + "connect-timeout": "1.6.2", + "content-type": "1.0.4", + "cookie": "0.1.3", + "cookie-parser": "1.3.5", + "cookie-signature": "1.0.6", + "csurf": "1.8.3", + "debug": "2.2.0", + "depd": "1.0.1", + "errorhandler": "1.4.3", + "express-session": "1.11.3", + "finalhandler": "0.4.0", + "fresh": "0.3.0", + "http-errors": "1.3.1", + "method-override": "2.3.10", + "morgan": "1.6.1", + "multiparty": "3.3.2", + "on-headers": "1.0.1", + "parseurl": "1.3.2", + "pause": "0.1.0", + "qs": "4.0.0", + "response-time": "2.3.2", + "serve-favicon": "2.3.2", + "serve-index": "1.7.3", + "serve-static": "1.10.3", + "type-is": "1.6.16", + "utils-merge": "1.0.0", + "vhost": "3.0.2" + }, + "dependencies": { + "bytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz", + "integrity": "sha1-rJPEEOL/ycx89LRks4KJBn9eR7Q=", + "dev": true + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "qs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", + "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=", + "dev": true + } + } + }, + "connect-livereload": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.5.4.tgz", + "integrity": "sha1-gBV9E3HJ83zBQDmrGJWXDRGdw7w=", + "dev": true + }, + "connect-timeout": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/connect-timeout/-/connect-timeout-1.6.2.tgz", + "integrity": "sha1-3ppexh4zoStu2qt7XwYumMWZuI4=", + "dev": true, + "requires": { + "debug": "2.2.0", + "http-errors": "1.3.1", + "ms": "0.7.1", + "on-headers": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "0.1.4" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=" + }, + "cookie": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz", + "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=", + "dev": true + }, + "cookie-parser": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz", + "integrity": "sha1-nXVVcPtdF4kHcSJ6AjFNm+fPg1Y=", + "dev": true, + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz", + "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "coveralls": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-2.13.3.tgz", + "integrity": "sha512-iiAmn+l1XqRwNLXhW8Rs5qHZRFMYp9ZIPjEOVRpC/c4so6Y/f4/lFi0FfR5B9cCqgyhkJ5cZmbvcVRfP8MHchw==", + "dev": true, + "requires": { + "js-yaml": "3.6.1", + "lcov-parse": "0.0.10", + "log-driver": "1.2.5", + "minimist": "1.2.0", + "request": "2.79.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "crc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.3.0.tgz", + "integrity": "sha1-+mIuG8OIvyVzCQgta2UgDOZwkLo=", + "dev": true + }, + "crc32-stream": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-0.3.4.tgz", + "integrity": "sha1-c7wltF+sHbZjIjGnv86JJ+nwZVI=", + "dev": true, + "requires": { + "buffer-crc32": "0.2.13", + "readable-stream": "1.0.34" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "create-ecdh": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.1.tgz", + "integrity": "sha512-iZvCCg8XqHQZ1ioNBTzXS/cQSkqkqcPs8xSX4upNB+DAk9Ht3uzQf2J32uAHNCne8LDmKr29AgZrEs4oIrwLuQ==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "elliptic": "6.4.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "1.0.4", + "inherits": "2.0.3", + "md5.js": "1.3.4", + "ripemd160": "2.0.2", + "sha.js": "2.4.11" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "1.0.4", + "create-hash": "1.2.0", + "inherits": "2.0.3", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.1", + "sha.js": "2.4.11" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.2", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "1.0.1", + "browserify-sign": "4.0.4", + "create-ecdh": "4.0.1", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "diffie-hellman": "5.0.3", + "inherits": "2.0.3", + "pbkdf2": "3.0.16", + "public-encrypt": "4.0.2", + "randombytes": "2.0.6", + "randomfill": "1.0.4" + } + }, + "csrf": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.0.6.tgz", + "integrity": "sha1-thEg3c7q/JHnbtUxO7XAsmZ7cQo=", + "dev": true, + "requires": { + "rndm": "1.2.0", + "tsscmp": "1.0.5", + "uid-safe": "2.1.4" + } + }, + "css": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.1.tgz", + "integrity": "sha1-c6TIHehdtmTU7mdPfUcIXjstVdw=", + "requires": { + "inherits": "2.0.3", + "source-map": "0.1.43", + "source-map-resolve": "0.3.1", + "urix": "0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "css-loader": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.9.1.tgz", + "integrity": "sha1-LhqgDOfjDvLGp6SzAKCAp8l54Nw=", + "dev": true, + "optional": true, + "requires": { + "csso": "1.3.12", + "loader-utils": "0.2.17", + "source-map": "0.1.43" + }, + "dependencies": { + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "optional": true, + "requires": { + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "optional": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "css-parse": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", + "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", + "dev": true, + "requires": { + "css": "2.2.1" + } + }, + "css-value": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", + "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=", + "dev": true + }, + "csso": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/csso/-/csso-1.3.12.tgz", + "integrity": "sha1-/GKGlKLTiTiqrEmWdTIY/TEc254=", + "dev": true, + "optional": true + }, + "csurf": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.8.3.tgz", + "integrity": "sha1-I/KhO/HY/OHQyZZYg5RELLqGpWo=", + "dev": true, + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6", + "csrf": "3.0.6", + "http-errors": "1.3.1" + } + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "1.0.2" + } + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "requires": { + "es5-ext": "0.10.42" + } + }, + "dargs": { + "version": "github:christian-bromann/dargs#7d6d4164a7c4106dbd14ef39ed8d95b7b5e9b770", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "data-uri-to-buffer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", + "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==", + "dev": true + }, + "date-format": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz", + "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=", + "dev": true + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "requires": { + "debug": "3.1.0", + "memoizee": "0.4.12", + "object-assign": "4.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + }, + "dependencies": { + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "deepmerge": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-0.2.10.tgz", + "integrity": "sha1-iQa/nlJaT78bIDsq/LRkAkmCEhk=", + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", + "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", + "dev": true, + "requires": { + "strip-bom": "2.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + } + } + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "1.0.4" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + } + } + }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "dev": true, + "requires": { + "foreach": "2.0.5", + "object-keys": "1.0.11" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + } + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "degenerator": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", + "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=", + "dev": true, + "requires": { + "ast-types": "0.11.3", + "escodegen": "1.8.1", + "esprima": "3.1.3" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + } + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", + "integrity": "sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo=", + "dev": true + }, + "deprecated": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", + "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detab": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.1.tgz", + "integrity": "sha512-/hhdqdQc5thGrqzjyO/pz76lDZ5GSuAs6goxOaKTsvPk7HNnzAyFN5lyHgqpX4/s1i66K8qMGj+VhA9504x7DQ==", + "dev": true, + "requires": { + "repeat-string": "1.6.1" + } + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" + }, + "detective": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", + "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", + "dev": true, + "requires": { + "acorn": "5.5.3", + "defined": "1.0.0" + } + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "miller-rabin": "4.0.1", + "randombytes": "2.0.6" + } + }, + "disparity": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/disparity/-/disparity-2.0.0.tgz", + "integrity": "sha1-V92stHMkrl9Y0swNqIbbTOnutxg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "diff": "1.4.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "doctrine-temporary-fork": { + "version": "2.0.0-alpha-allowarrayindex", + "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.0.0-alpha-allowarrayindex.tgz", + "integrity": "sha1-QAFahn6yfnWybIKLcVJPE3+J+fA=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "documentation": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-5.5.0.tgz", + "integrity": "sha512-Aod3HOI+8zMhwWztDlECRsDfJ8SFu4oADvipOLq3gnWKy4Cpg2oF5AWT+U6PcX85KuguDI6c+q+2YwYEx99B/A==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "babel-core": "6.26.0", + "babel-generator": "6.26.1", + "babel-plugin-system-import-transformer": "3.1.0", + "babel-plugin-transform-decorators-legacy": "1.3.4", + "babel-preset-env": "1.6.1", + "babel-preset-react": "6.24.1", + "babel-preset-stage-0": "6.24.1", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babelify": "8.0.0", + "babylon": "6.18.0", + "chalk": "2.4.0", + "chokidar": "2.0.3", + "concat-stream": "1.6.2", + "disparity": "2.0.0", + "doctrine-temporary-fork": "2.0.0-alpha-allowarrayindex", + "get-port": "3.2.0", + "git-url-parse": "8.3.1", + "github-slugger": "1.2.0", + "glob": "7.1.2", + "globals-docs": "2.4.0", + "highlight.js": "9.12.0", + "js-yaml": "3.11.0", + "lodash": "4.17.5", + "mdast-util-inject": "1.1.0", + "micromatch": "3.1.10", + "mime": "1.6.0", + "module-deps-sortable": "4.0.6", + "parse-filepath": "1.0.2", + "pify": "3.0.0", + "read-pkg-up": "3.0.0", + "remark": "9.0.0", + "remark-html": "7.0.0", + "remark-reference-links": "4.0.1", + "remark-toc": "5.0.0", + "remote-origin-url": "0.4.0", + "shelljs": "0.8.1", + "stream-array": "1.1.2", + "strip-json-comments": "2.0.1", + "tiny-lr": "1.1.1", + "unist-builder": "1.0.2", + "unist-util-visit": "1.3.0", + "vfile": "2.3.0", + "vfile-reporter": "4.0.0", + "vfile-sort": "2.1.0", + "vinyl": "2.1.0", + "vinyl-fs": "3.0.2", + "yargs": "9.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "babel-core": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", + "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.1", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.5.1", + "debug": "2.6.9", + "json5": "0.5.1", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.8", + "slash": "1.0.0", + "source-map": "0.5.7" + } + }, + "chalk": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "js-yaml": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "yargs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", + "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "dev": true, + "requires": { + "camelcase": "4.1.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "read-pkg-up": "2.0.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "7.0.0" + }, + "dependencies": { + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + } + } + } + } + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "1.0.1", + "ent": "2.2.0", + "extend": "3.0.1", + "void-elements": "2.0.1" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=", + "dev": true, + "optional": true + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "2.3.6" + } + }, + "duplexify": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.4.tgz", + "integrity": "sha512-JzYSLYMhoVVBe8+mbHQ4KgpvHpm0DZpJuL8PY93Vyv1fW7jYJ90LoXa1di/CVbJM+TgMs91rbDapE/RNIfnJsA==", + "dev": true, + "requires": { + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "ejs": { + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.9.tgz", + "integrity": "sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.42", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.42.tgz", + "integrity": "sha1-lcM78B0MxAVVauyJn+Yf1NduoPk=", + "dev": true + }, + "elliptic": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "brorand": "1.1.0", + "hash.js": "1.1.3", + "hmac-drbg": "1.0.1", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "emoji-regex": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", + "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=", + "dev": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "engine.io": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.5.tgz", + "integrity": "sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA==", + "dev": true, + "requires": { + "accepts": "1.3.5", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "3.1.0", + "engine.io-parser": "2.1.2", + "uws": "9.14.0", + "ws": "3.3.3" + }, + "dependencies": { + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "2.1.18", + "negotiator": "0.6.1" + } + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + } + } + }, + "engine.io-client": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.6.tgz", + "integrity": "sha512-hnuHsFluXnsKOndS4Hv6SvUrgdYx1pk2NqfaDMW+GWdgfU3+/V25Cj7I8a0x92idSpa5PIhJRKxPvp9mnoLsfg==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "3.1.0", + "engine.io-parser": "2.1.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "3.3.3", + "xmlhttprequest-ssl": "1.5.5", + "yeast": "0.1.2" + } + }, + "engine.io-parser": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz", + "integrity": "sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw==", + "dev": true, + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.4", + "has-binary2": "1.0.2" + } + }, + "enhanced-resolve": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", + "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "memory-fs": "0.4.1", + "object-assign": "4.1.1", + "tapable": "0.2.8" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "1.0.1" + } + }, + "error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", + "dev": true, + "requires": { + "string-template": "0.2.1", + "xtend": "4.0.1" + } + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "errorhandler": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.4.3.tgz", + "integrity": "sha1-t7cO2PNZ6duICS8tIMD4MUIK2D8=", + "dev": true, + "requires": { + "accepts": "1.3.5", + "escape-html": "1.0.3" + }, + "dependencies": { + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "2.1.18", + "negotiator": "0.6.1" + } + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + } + } + }, + "es5-ext": { + "version": "0.10.42", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.42.tgz", + "integrity": "sha512-AJxO1rmPe1bDEfSR6TJ/FgMFYuTBhR5R57KW58iCkYACMyFbrkqVyzXSurYoScDGvgyMpk7uRF/lPUPPTmsRSA==", + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" + } + }, + "es5-shim": { + "version": "4.5.10", + "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.5.10.tgz", + "integrity": "sha512-vmryBdqKRO8Ei9LJ4yyEk/EOmAOGIagcHDYPpTAi6pot4IMHS1AC2q5cTKPmydpijg2iX8DVmCuqgrNxIWj8Yg==", + "dev": true + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.42", + "es6-symbol": "3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.42", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.42", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.42" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.42", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "2.7.3", + "estraverse": "1.9.3", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.2.0" + }, + "dependencies": { + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "0.1.5", + "es6-weak-map": "2.0.2", + "esrecurse": "4.2.1", + "estraverse": "4.2.0" + } + }, + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "babel-code-frame": "6.26.0", + "chalk": "2.4.0", + "concat-stream": "1.6.2", + "cross-spawn": "5.1.0", + "debug": "3.1.0", + "doctrine": "2.1.0", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "1.0.0", + "espree": "3.5.4", + "esquery": "1.0.1", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.2", + "globals": "11.4.0", + "ignore": "3.3.7", + "imurmurhash": "0.1.4", + "inquirer": "3.3.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.11.0", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.0", + "regexpp": "1.1.0", + "require-uncached": "1.0.3", + "semver": "5.5.0", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "2.0.2" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "globals": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.4.0.tgz", + "integrity": "sha512-Dyzmifil8n/TmSqYDEXbm+C8yitzJQqQIlJQLNRMwa+BOUJpRC19pyVeN12JAjt61xonvXjtff+hJruTRXn5HA==", + "dev": true + }, + "js-yaml": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "eslint-config-standard": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", + "integrity": "sha1-wGHk0GbzedwXzVYsZOgZtN1FRZE=", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "2.6.9", + "resolve": "1.7.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-module-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", + "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", + "dev": true, + "requires": { + "debug": "2.6.9", + "pkg-dir": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.11.0.tgz", + "integrity": "sha1-Fa7qN6Z0mdhI6OmBgG1GJ7VQOBY=", + "dev": true, + "requires": { + "contains-path": "0.1.0", + "debug": "2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "0.3.2", + "eslint-module-utils": "2.2.0", + "has": "1.0.1", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "read-pkg-up": "2.0.0", + "resolve": "1.7.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "2.3.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + } + } + } + }, + "eslint-plugin-node": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-5.2.1.tgz", + "integrity": "sha512-xhPXrh0Vl/b7870uEbaumb2Q+LxaEcOQ3kS1jtIXanBAwpMre1l5q/l2l/hESYJGEFKuI78bp6Uw50hlpr7B+g==", + "dev": true, + "requires": { + "ignore": "3.3.7", + "minimatch": "3.0.4", + "resolve": "1.7.1", + "semver": "5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, + "eslint-plugin-promise": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.7.0.tgz", + "integrity": "sha512-2WO+ZFh7vxUKRfR0cOIMrWgYKdR6S1AlOezw6pC52B6oYpd5WFghN+QHxvrRdZMtbo8h3dfUZ2o1rWb0UPbKtg==", + "dev": true + }, + "eslint-plugin-standard": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz", + "integrity": "sha1-NNDJFbRe3G8BA5PH7vOCOwhWXPI=", + "dev": true + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "4.2.1", + "estraverse": "4.2.0" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "5.5.3", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "estree-walker": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.3.1.tgz", + "integrity": "sha1-5rGlHPcpJSTnI3wxLl/mZgwc4ao=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", + "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.42" + } + }, + "event-stream": { + "version": "3.3.4", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "0.1.1", + "from": "0.1.7", + "map-stream": "0.1.0", + "pause-stream": "0.0.11", + "split": "0.3.3", + "stream-combiner": "0.0.4", + "through": "2.3.8" + } + }, + "eventemitter3": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.0.1.tgz", + "integrity": "sha512-QOCPu979MMWX9XNlfRZoin+Wm+bK1SP7vv3NGUniYwuSJK/+cPA10blMaeRgzg31RvoSFk6FsCDVa4vNryBTGA==", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "1.3.4", + "safe-buffer": "5.1.1" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + }, + "expand-braces": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", + "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", + "dev": true, + "requires": { + "array-slice": "0.2.3", + "array-unique": "0.2.1", + "braces": "0.1.5" + }, + "dependencies": { + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", + "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", + "dev": true, + "requires": { + "expand-range": "0.1.1" + } + }, + "expand-range": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", + "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", + "dev": true, + "requires": { + "is-number": "0.1.1", + "repeat-string": "0.2.2" + } + }, + "is-number": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", + "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", + "dev": true + }, + "repeat-string": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", + "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", + "dev": true + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "2.2.3" + }, + "dependencies": { + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "1.0.1" + } + }, + "express-session": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.11.3.tgz", + "integrity": "sha1-XMmPP1/4Ttg1+Ry/CqvQxxB0AK8=", + "dev": true, + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6", + "crc": "3.3.0", + "debug": "2.2.0", + "depd": "1.0.1", + "on-headers": "1.0.1", + "parseurl": "1.3.2", + "uid-safe": "2.0.0", + "utils-merge": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "uid-safe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.0.0.tgz", + "integrity": "sha1-p/PGymSh9qXQTsDvPkw9U2cxcTc=", + "dev": true, + "requires": { + "base64-url": "1.2.1" + } + } + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "requires": { + "chardet": "0.4.2", + "iconv-lite": "0.4.21", + "tmp": "0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", + "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "faker": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/faker/-/faker-3.1.0.tgz", + "integrity": "sha1-D5CPr05uwCUk5UpX5DLFwBPgjJ8=", + "dev": true + }, + "fancy-log": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", + "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", + "dev": true, + "requires": { + "ansi-gray": "0.1.1", + "color-support": "1.1.3", + "time-stamp": "1.1.0" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": "0.7.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, + "file-loader": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-0.8.5.tgz", + "integrity": "sha1-knXQMf54DyfUf19K8CvUNxPMFRs=", + "dev": true, + "optional": true, + "requires": { + "loader-utils": "0.2.17" + }, + "dependencies": { + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "optional": true, + "requires": { + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" + } + } + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "7.1.2", + "minimatch": "3.0.4" + } + }, + "fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", + "dev": true, + "requires": { + "is-object": "1.0.1", + "merge-descriptors": "1.0.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "finalhandler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", + "integrity": "sha1-llpS2ejQXSuFdUhUH7ibU6JJfZs=", + "dev": true, + "requires": { + "debug": "2.2.0", + "escape-html": "1.0.2", + "on-finished": "2.3.0", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "escape-html": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz", + "integrity": "sha1-130y+pjjjC9BroXpJ44ODmuhAiw=", + "dev": true + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "1.0.1", + "make-dir": "1.2.0", + "pkg-dir": "2.0.0" + } + }, + "find-index": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", + "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=", + "dev": true + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "1.0.0", + "is-glob": "3.1.0", + "micromatch": "3.1.10", + "resolve-dir": "1.0.1" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + } + } + }, + "fined": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz", + "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "is-plain-object": "2.0.4", + "object.defaults": "1.1.0", + "object.pick": "1.3.0", + "parse-filepath": "1.0.2" + } + }, + "first-chunk-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", + "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", + "dev": true + }, + "flagged-respawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz", + "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=", + "dev": true + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "follow-redirects": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz", + "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==", + "dev": true, + "requires": { + "debug": "3.1.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, + "foreachasync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", + "integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "fork-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", + "integrity": "sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=", + "dev": true + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "0.2.2" + } + }, + "fresh": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", + "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=", + "dev": true + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, + "fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "dev": true, + "requires": { + "null-check": "1.0.0" + } + }, + "fs-extra": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", + "integrity": "sha1-9G8MdbeEH40gCzNIzU1pHVoJnRU=", + "dev": true, + "requires": { + "jsonfile": "1.0.1", + "mkdirp": "0.3.5", + "ncp": "0.4.2", + "rimraf": "2.2.8" + }, + "dependencies": { + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + } + } + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "through2": "2.0.3" + } + }, + "fs.extra": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz", + "integrity": "sha1-3QI/kwE77iRTHxszUUw3sg/ZM0k=", + "dev": true, + "requires": { + "fs-extra": "0.6.4", + "mkdirp": "0.3.5", + "walk": "2.3.13" + }, + "dependencies": { + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.2.tgz", + "integrity": "sha512-iownA+hC4uHFp+7gwP/y5SzaiUo7m2vpa0dhpzw8YuKtiZsz7cIXsFbXpLEeBM6WuCQyw1MH4RRe6XI8GFUctQ==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.10.0", + "node-pre-gyp": "0.9.1" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "2.2.4" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.1.1", + "yallist": "3.0.2" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "2.2.4" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.9", + "iconv-lite": "0.4.21", + "sax": "1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.9.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.0", + "nopt": "4.0.1", + "npm-packlist": "1.1.10", + "npmlog": "4.1.2", + "rc": "1.2.6", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "4.4.1" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.2.4", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.1", + "yallist": "3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "fstream": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-0.1.31.tgz", + "integrity": "sha1-czfwWPu7vvqMn1YaKMqwhJICyYg=", + "dev": true, + "requires": { + "graceful-fs": "3.0.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + }, + "dependencies": { + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "dev": true, + "requires": { + "natives": "1.1.3" + } + } + } + }, + "ftp": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", + "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", + "dev": true, + "requires": { + "readable-stream": "1.1.14", + "xregexp": "2.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gaze": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", + "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", + "dev": true, + "requires": { + "globule": "0.1.0" + } + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "1.0.2" + } + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true + }, + "get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.1.tgz", + "integrity": "sha512-7aelVrYqCLuVjq2kEKRTH8fXPTC0xKTkM+G7UlFkEwCXY3sFbSxvY375JoFowOAYbkaU47SrBvOefUlLZZ+6QA==", + "dev": true, + "requires": { + "data-uri-to-buffer": "1.2.0", + "debug": "2.6.9", + "extend": "3.0.1", + "file-uri-to-path": "1.0.0", + "ftp": "0.3.10", + "readable-stream": "2.3.6" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "git-up": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-2.0.10.tgz", + "integrity": "sha512-2v4UN3qV2RGypD9QpmUjpk+4+RlYpW8GFuiZqQnKmvei08HsFPd0RfbDvEhnE4wBvnYs8ORVtYpOFuuCEmBVBw==", + "dev": true, + "requires": { + "is-ssh": "1.3.0", + "parse-url": "1.3.11" + } + }, + "git-url-parse": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-8.3.1.tgz", + "integrity": "sha512-r/FxXIdfgdSO+V2zl4ZK1JGYkHT9nqVRSzom5WsYPLg3XzeBeKPl3R/6X9E9ZJRx/sE/dXwXtfl+Zp7YL8ktWQ==", + "dev": true, + "requires": { + "git-up": "2.0.10", + "parse-domain": "2.0.0" + } + }, + "github-slugger": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.2.0.tgz", + "integrity": "sha512-wIaa75k1vZhyPm9yWrD08A5Xnx/V+RmzGrpjQuLemGKSb77Qukiaei58Bogrl/LZSADDfPzKJX8jhLs4CRTl7Q==", + "dev": true, + "requires": { + "emoji-regex": "6.1.1" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + }, + "dependencies": { + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "3.1.0", + "path-dirname": "1.0.2" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + } + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "requires": { + "extend": "3.0.1", + "glob": "7.1.2", + "glob-parent": "3.1.0", + "is-negated-glob": "1.0.0", + "ordered-read-streams": "1.0.1", + "pumpify": "1.4.0", + "readable-stream": "2.3.6", + "remove-trailing-separator": "1.1.0", + "to-absolute-glob": "2.0.2", + "unique-stream": "2.2.1" + } + }, + "glob-watcher": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", + "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", + "dev": true, + "requires": { + "gaze": "0.5.2" + } + }, + "glob2base": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", + "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", + "dev": true, + "requires": { + "find-index": "0.1.1" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "1.0.2", + "is-windows": "1.0.2", + "resolve-dir": "1.0.1" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "homedir-polyfill": "1.0.1", + "ini": "1.3.5", + "is-windows": "1.0.2", + "which": "1.3.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globals-docs": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/globals-docs/-/globals-docs-2.4.0.tgz", + "integrity": "sha512-B69mWcqCmT3jNYmSxRxxOXWfzu3Go8NQXPfl2o0qPd1EEFhwW0dFUg9ztTu915zPQzqwIhWAlw6hmfIcCK4kkQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "globule": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", + "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", + "dev": true, + "requires": { + "glob": "3.1.21", + "lodash": "1.0.2", + "minimatch": "0.2.14" + }, + "dependencies": { + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "requires": { + "graceful-fs": "1.2.3", + "inherits": "1.0.2", + "minimatch": "0.2.14" + } + }, + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "lodash": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + } + } + }, + "glogg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz", + "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==", + "dev": true, + "requires": { + "sparkles": "1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.8.1.tgz", + "integrity": "sha1-Sy3sjZB+k9szZiTc7AGDUC+MlCg=", + "dev": true + }, + "gulp": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", + "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", + "dev": true, + "requires": { + "archy": "1.0.0", + "chalk": "1.1.3", + "deprecated": "0.0.1", + "gulp-util": "3.0.8", + "interpret": "1.1.0", + "liftoff": "2.5.0", + "minimist": "1.2.0", + "orchestrator": "0.3.8", + "pretty-hrtime": "1.0.3", + "semver": "4.3.6", + "tildify": "1.2.0", + "v8flags": "2.1.1", + "vinyl-fs": "0.3.14" + }, + "dependencies": { + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "glob": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "2.0.10", + "once": "1.4.0" + } + }, + "glob-stream": { + "version": "3.1.18", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", + "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", + "dev": true, + "requires": { + "glob": "4.5.3", + "glob2base": "0.0.12", + "minimatch": "2.0.10", + "ordered-read-streams": "0.1.0", + "through2": "0.6.5", + "unique-stream": "1.0.0" + } + }, + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "dev": true, + "requires": { + "natives": "1.1.3" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "ordered-read-streams": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", + "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-bom": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", + "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", + "dev": true, + "requires": { + "first-chunk-stream": "1.0.0", + "is-utf8": "0.2.1" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "4.0.1" + } + }, + "unique-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", + "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=", + "dev": true + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true, + "requires": { + "clone": "0.2.0", + "clone-stats": "0.0.1" + } + }, + "vinyl-fs": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", + "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", + "dev": true, + "requires": { + "defaults": "1.0.3", + "glob-stream": "3.1.18", + "glob-watcher": "0.0.6", + "graceful-fs": "3.0.11", + "mkdirp": "0.5.1", + "strip-bom": "1.0.0", + "through2": "0.6.5", + "vinyl": "0.4.6" + } + } + } + }, + "gulp-babel": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-6.1.3.tgz", + "integrity": "sha512-tm15R3rt4gO59WXCuqrwf4QXJM9VIJC+0J2NPYSC6xZn+cZRD5y5RPGAiHaDxCJq7Rz5BDljlrk3cEjWADF+wQ==", + "dev": true, + "requires": { + "babel-core": "6.26.0", + "object-assign": "4.1.1", + "plugin-error": "1.0.1", + "replace-ext": "0.0.1", + "through2": "2.0.3", + "vinyl-sourcemaps-apply": "0.2.1" + }, + "dependencies": { + "babel-core": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", + "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.1", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.5.1", + "debug": "2.6.9", + "json5": "0.5.1", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.8", + "slash": "1.0.0", + "source-map": "0.5.7" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "gulp-clean": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/gulp-clean/-/gulp-clean-0.3.2.tgz", + "integrity": "sha1-o0fUc6zqQBgvk1WHpFGUFnGSgQI=", + "dev": true, + "requires": { + "gulp-util": "2.2.20", + "rimraf": "2.6.2", + "through2": "0.4.2" + }, + "dependencies": { + "ansi-regex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "dev": true + }, + "ansi-styles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", + "dev": true + }, + "chalk": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "dev": true, + "requires": { + "ansi-styles": "1.1.0", + "escape-string-regexp": "1.0.5", + "has-ansi": "0.1.0", + "strip-ansi": "0.3.0", + "supports-color": "0.2.0" + } + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "4.0.1", + "meow": "3.7.0" + } + }, + "gulp-util": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", + "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", + "dev": true, + "requires": { + "chalk": "0.5.1", + "dateformat": "1.0.12", + "lodash._reinterpolate": "2.4.1", + "lodash.template": "2.4.1", + "minimist": "0.2.0", + "multipipe": "0.1.2", + "through2": "0.5.1", + "vinyl": "0.2.3" + }, + "dependencies": { + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "3.0.0" + } + } + } + }, + "has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "dev": true, + "requires": { + "ansi-regex": "0.2.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", + "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=", + "dev": true + }, + "lodash.escape": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", + "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", + "dev": true, + "requires": { + "lodash._escapehtmlchar": "2.4.1", + "lodash._reunescapedhtml": "2.4.1", + "lodash.keys": "2.4.1" + } + }, + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + }, + "lodash.template": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", + "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", + "dev": true, + "requires": { + "lodash._escapestringchar": "2.4.1", + "lodash._reinterpolate": "2.4.1", + "lodash.defaults": "2.4.1", + "lodash.escape": "2.4.1", + "lodash.keys": "2.4.1", + "lodash.templatesettings": "2.4.1", + "lodash.values": "2.4.1" + } + }, + "lodash.templatesettings": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", + "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", + "dev": true, + "requires": { + "lodash._reinterpolate": "2.4.1", + "lodash.escape": "2.4.1" + } + }, + "minimist": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", + "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", + "dev": true + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "dev": true, + "requires": { + "ansi-regex": "0.2.1" + } + }, + "supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true + }, + "through2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "2.1.2" + }, + "dependencies": { + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "0.4.0" + } + } + } + }, + "vinyl": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", + "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + "dev": true, + "requires": { + "clone-stats": "0.0.1" + } + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + } + } + }, + "gulp-concat": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", + "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", + "dev": true, + "requires": { + "concat-with-sourcemaps": "1.0.5", + "through2": "2.0.3", + "vinyl": "2.1.0" + } + }, + "gulp-connect": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gulp-connect/-/gulp-connect-5.0.0.tgz", + "integrity": "sha1-8v3zBq6RFGg2jCKF8teC8T7dr04=", + "dev": true, + "requires": { + "connect": "2.30.2", + "connect-livereload": "0.5.4", + "event-stream": "3.3.4", + "gulp-util": "3.0.8", + "tiny-lr": "0.2.1" + }, + "dependencies": { + "body-parser": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", + "integrity": "sha1-EBXLH+LEQ4WCWVgdtTMy+NDPUPk=", + "dev": true, + "requires": { + "bytes": "2.2.0", + "content-type": "1.0.4", + "debug": "2.2.0", + "depd": "1.1.2", + "http-errors": "1.3.1", + "iconv-lite": "0.4.13", + "on-finished": "2.3.0", + "qs": "5.2.0", + "raw-body": "2.1.7", + "type-is": "1.6.16" + }, + "dependencies": { + "qs": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", + "integrity": "sha1-qfMRQq9GjLcrJbMBNrokVoNJFr4=", + "dev": true + } + } + }, + "bytes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", + "integrity": "sha1-/TVGSkA/b5EXwt42Cez/nK4ABYg=", + "dev": true + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", + "dev": true + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "qs": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz", + "integrity": "sha1-TZMuXH6kEcynajEtOaYGIA/VDNk=", + "dev": true + }, + "raw-body": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "dev": true, + "requires": { + "bytes": "2.4.0", + "iconv-lite": "0.4.13", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", + "dev": true + } + } + }, + "tiny-lr": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", + "integrity": "sha1-s/26gC5dVqM8L28QeUsy5Hescp0=", + "dev": true, + "requires": { + "body-parser": "1.14.2", + "debug": "2.2.0", + "faye-websocket": "0.10.0", + "livereload-js": "2.3.0", + "parseurl": "1.3.2", + "qs": "5.1.0" + } + } + } + }, + "gulp-documentation": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/gulp-documentation/-/gulp-documentation-3.2.1.tgz", + "integrity": "sha1-r1JKv9cuI+cVXwCyoYoHo2QqjdU=", + "dev": true, + "requires": { + "through2": "2.0.3", + "vinyl": "2.1.0" + } + }, + "gulp-eslint": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-4.0.2.tgz", + "integrity": "sha512-fcFUQzFsN6dJ6KZlG+qPOEkqfcevRUXgztkYCvhNvJeSvOicC8ucutN4qR/ID8LmNZx9YPIkBzazTNnVvbh8wg==", + "dev": true, + "requires": { + "eslint": "4.19.1", + "fancy-log": "1.3.2", + "plugin-error": "1.0.1" + } + }, + "gulp-footer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/gulp-footer/-/gulp-footer-1.1.2.tgz", + "integrity": "sha512-G6Z8DNNeIhq1KU++7kZnbuwbvCubkUMOVADOt+0qTHSIqjy2OPo1W4bu4n1aE9JGZncuRTvVQrYecGx2uazlpg==", + "dev": true, + "requires": { + "event-stream": "3.3.4", + "lodash._reescape": "3.0.0", + "lodash._reevaluate": "3.0.0", + "lodash._reinterpolate": "3.0.0", + "lodash.template": "3.6.2" + } + }, + "gulp-header": { + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz", + "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==", + "dev": true, + "requires": { + "concat-with-sourcemaps": "1.0.5", + "lodash.template": "4.4.0", + "through2": "2.0.3" + }, + "dependencies": { + "lodash.template": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", + "integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=", + "dev": true, + "requires": { + "lodash._reinterpolate": "3.0.0", + "lodash.templatesettings": "4.1.0" + } + }, + "lodash.templatesettings": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", + "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", + "dev": true, + "requires": { + "lodash._reinterpolate": "3.0.0" + } + } + } + }, + "gulp-if": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-2.0.2.tgz", + "integrity": "sha1-pJe351cwBQQcqivIt92jyARE1ik=", + "dev": true, + "requires": { + "gulp-match": "1.0.3", + "ternary-stream": "2.0.1", + "through2": "2.0.3" + } + }, + "gulp-js-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gulp-js-escape/-/gulp-js-escape-1.0.1.tgz", + "integrity": "sha1-HNRF+9AJ4Np2lZoDp/SbNWav+Gg=", + "dev": true, + "requires": { + "through2": "0.6.5" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "4.0.1" + } + } + } + }, + "gulp-match": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.0.3.tgz", + "integrity": "sha1-kcfA1/Kb7NZgbVfYCn+Hdqh6uo4=", + "dev": true, + "requires": { + "minimatch": "3.0.4" + } + }, + "gulp-optimize-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-optimize-js/-/gulp-optimize-js-1.1.0.tgz", + "integrity": "sha1-X9FcaLNvbh5zh3hPhXhDX3VpYkU=", + "dev": true, + "requires": { + "gulp-util": "3.0.8", + "lodash": "4.17.5", + "optimize-js": "1.0.3", + "through2": "2.0.3" + } + }, + "gulp-rename": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.2.2.tgz", + "integrity": "sha1-OtRCh2PwXidk3sHGfYaNsnVoeBc=", + "dev": true + }, + "gulp-replace": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-0.4.0.tgz", + "integrity": "sha1-4ivJwD6dBRsyiBzFib0+jE5UFoo=", + "dev": true, + "requires": { + "event-stream": "3.0.20", + "istextorbinary": "1.0.2", + "replacestream": "0.1.3" + }, + "dependencies": { + "event-stream": { + "version": "3.0.20", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.0.20.tgz", + "integrity": "sha1-A4u7LqnqkDhbJvvBhU0LU58qvqM=", + "dev": true, + "requires": { + "duplexer": "0.1.1", + "from": "0.1.7", + "map-stream": "0.0.7", + "pause-stream": "0.0.11", + "split": "0.2.10", + "stream-combiner": "0.0.4", + "through": "2.3.8" + } + }, + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, + "split": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz", + "integrity": "sha1-Zwl8YB1pfOE2j0GPBs0gHPBSGlc=", + "dev": true, + "requires": { + "through": "2.3.8" + } + } + } + }, + "gulp-shell": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/gulp-shell/-/gulp-shell-0.5.2.tgz", + "integrity": "sha1-pJWcoGUa0ce7/nCy0K27tOGuqY0=", + "dev": true, + "requires": { + "async": "1.5.2", + "gulp-util": "3.0.8", + "lodash": "4.17.5", + "through2": "2.0.3" + } + }, + "gulp-sourcemaps": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.4.tgz", + "integrity": "sha1-y7IAhFCxvM5s0jv5gze+dRv24wo=", + "requires": { + "@gulp-sourcemaps/identity-map": "1.0.1", + "@gulp-sourcemaps/map-sources": "1.0.0", + "acorn": "5.5.3", + "convert-source-map": "1.5.1", + "css": "2.2.1", + "debug-fabulous": "1.1.0", + "detect-newline": "2.1.0", + "graceful-fs": "4.1.11", + "source-map": "0.6.1", + "strip-bom-string": "1.0.0", + "through2": "2.0.3" + } + }, + "gulp-uglify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.0.tgz", + "integrity": "sha1-DfAzHXKg0wLj434QlIXd3zPG0co=", + "dev": true, + "requires": { + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash": "4.17.5", + "make-error-cause": "1.2.2", + "through2": "2.0.3", + "uglify-js": "3.3.22", + "vinyl-sourcemaps-apply": "0.2.1" + }, + "dependencies": { + "uglify-js": { + "version": "3.3.22", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.22.tgz", + "integrity": "sha512-tqw96rL6/BG+7LM5VItdhDjTQmL5zG/I0b2RqWytlgeHe2eydZHuBHdA9vuGpCDhH/ZskNGcqDhivoR2xt8RIw==", + "dev": true, + "requires": { + "commander": "2.15.1", + "source-map": "0.6.1" + } + } + } + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "dev": true, + "requires": { + "array-differ": "1.0.0", + "array-uniq": "1.0.3", + "beeper": "1.1.1", + "chalk": "1.1.3", + "dateformat": "2.2.0", + "fancy-log": "1.3.2", + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash._reescape": "3.0.0", + "lodash._reevaluate": "3.0.0", + "lodash._reinterpolate": "3.0.0", + "lodash.template": "3.6.2", + "minimist": "1.2.0", + "multipipe": "0.1.2", + "object-assign": "3.0.0", + "replace-ext": "0.0.1", + "through2": "2.0.3", + "vinyl": "0.5.3" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "requires": { + "clone": "1.0.4", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "gulp-webdriver": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/gulp-webdriver/-/gulp-webdriver-1.0.3.tgz", + "integrity": "sha1-mM6Bz5rganoZB7htEPaThvQ4Oi0=", + "dev": true, + "requires": { + "dargs": "github:christian-bromann/dargs#7d6d4164a7c4106dbd14ef39ed8d95b7b5e9b770", + "deepmerge": "0.2.10", + "gulp-util": "3.0.8", + "through2": "0.6.5", + "webdriverio": "3.4.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "4.0.1" + } + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "1.0.1" + } + }, + "handlebars": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", + "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "dev": true, + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "commander": "2.15.1", + "is-my-json-valid": "2.17.2", + "pinkie-promise": "2.0.1" + } + }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true, + "requires": { + "function-bind": "1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-binary2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz", + "integrity": "sha1-6D26SfC5vk0CbSc2U1DZ8D9Uvpg=", + "dev": true, + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "1.0.0" + } + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1" + } + }, + "hast-util-is-element": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.0.0.tgz", + "integrity": "sha1-P3IWl4sq4U2YdJh4eCZ18zvjzgA=", + "dev": true + }, + "hast-util-sanitize": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-1.1.2.tgz", + "integrity": "sha1-0QvWdXoh5ZwTq8iuNTDdO219Z54=", + "dev": true, + "requires": { + "xtend": "4.0.1" + } + }, + "hast-util-to-html": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-3.1.0.tgz", + "integrity": "sha1-iCyZhJ5AEw6ZHAQuRW1FPZXDbP8=", + "dev": true, + "requires": { + "ccount": "1.0.3", + "comma-separated-tokens": "1.0.5", + "hast-util-is-element": "1.0.0", + "hast-util-whitespace": "1.0.0", + "html-void-elements": "1.0.3", + "kebab-case": "1.0.0", + "property-information": "3.2.0", + "space-separated-tokens": "1.1.2", + "stringify-entities": "1.3.1", + "unist-util-is": "2.1.1", + "xtend": "4.0.1" + } + }, + "hast-util-whitespace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.0.tgz", + "integrity": "sha1-vQlpGWJdKTbh/xe8Tff9cn8X7Ok=", + "dev": true + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "highlight.js": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz", + "integrity": "sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4=", + "dev": true + }, + "hipchat-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hipchat-notifier/-/hipchat-notifier-1.1.0.tgz", + "integrity": "sha1-ttJJdVQ3wZEII2d5nTupoPI7Ix4=", + "dev": true, + "optional": true, + "requires": { + "lodash": "4.17.5", + "request": "2.79.0" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "1.1.3", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "dev": true, + "requires": { + "parse-passwd": "1.0.0" + } + }, + "hosted-git-info": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "dev": true + }, + "html-void-elements": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.3.tgz", + "integrity": "sha512-SaGhCDPXJVNrQyKMtKy24q6IMdXg5FCPN3z+xizxw9l+oXQw5fOoaj/ERU5KqWhSYhXtW5bWthlDbTDLBhJQrA==", + "dev": true + }, + "http-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "statuses": "1.5.0" + } + }, + "http-parser-js": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.11.tgz", + "integrity": "sha512-QCR5O2AjjMW8Mo4HyI1ctFcv+O99j/0g367V3YoVnrNw5hkDvAWZD0lWGcc+F4yN3V55USPCVix4efb75HxFfA==", + "dev": true + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "requires": { + "eventemitter3": "3.0.1", + "follow-redirects": "1.4.1", + "requires-port": "1.0.0" + } + }, + "http-proxy-agent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz", + "integrity": "sha1-zBzjjkU7+YSg93AtLdWcc9CBKEo=", + "dev": true, + "requires": { + "agent-base": "2.1.1", + "debug": "2.6.9", + "extend": "3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" + } + }, + "httpntlm": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz", + "integrity": "sha1-rQFScUOi6Hc8+uapb1hla7UqNLI=", + "dev": true, + "requires": { + "httpreq": "0.4.24", + "underscore": "1.7.0" + } + }, + "httpreq": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.24.tgz", + "integrity": "sha1-QzX/2CzZaWaKOUZckprGHWOTYn8=", + "dev": true + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", + "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", + "dev": true, + "requires": { + "agent-base": "2.1.1", + "debug": "2.6.9", + "extend": "3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "iconv-lite": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", + "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=", + "dev": true + }, + "ieee754": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.11.tgz", + "integrity": "sha512-VhDzCKN7K8ufStx/CLj5/PDTMgph+qwN5Pkd5i0sGnVwk56zJ0lkT8Qzi1xqWLS0Wp29DgDtNeS7v8/wMoZeHg==", + "dev": true + }, + "ignore": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", + "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "dev": true + }, + "ignore-loader": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ignore-loader/-/ignore-loader-0.1.2.tgz", + "integrity": "sha1-2B8kA3bQuk8Nd4lyw60lh0EXpGM=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflection": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.10.0.tgz", + "integrity": "sha1-W//LEZetPoEFD44X4hZoCH7p6y8=", + "dev": true, + "optional": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.1.0", + "chalk": "2.4.0", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.2.0", + "figures": "2.0.0", + "lodash": "4.17.5", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ip": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.0.1.tgz", + "integrity": "sha1-x+NWzeoiWucbNtcPLnGpK6TkJZA=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "1.0.0", + "is-windows": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-alphabetical": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.2.tgz", + "integrity": "sha512-V0xN4BYezDHcBSKb1QHUFMlR4as/XEuCZBzMJUU4n7+Cbt33SmUnSol+pnXFvLxSHNq2CemUXNdaXV6Flg7+xg==", + "dev": true + }, + "is-alphanumeric": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz", + "integrity": "sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=", + "dev": true + }, + "is-alphanumerical": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz", + "integrity": "sha512-pyfU/0kHdISIgslFfZN9nfY1Gk3MquQgUm1mJTjdkEPpkAKNWuBTSqFwewOpR7N351VkErCiyV71zX7mlQQqsg==", + "dev": true, + "requires": { + "is-alphabetical": "1.0.2", + "is-decimal": "1.0.2" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "1.11.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-decimal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.2.tgz", + "integrity": "sha512-TRzl7mOCchnhchN+f3ICUCzYvL9ul7R+TYOsZ8xia++knyZAJfv/uA1FvQXsAnYIl1T3B2X5E/J7Wb1QXiIBXg==", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-generator": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-generator/-/is-generator-1.0.3.tgz", + "integrity": "sha1-wUwhBX7TbjKNuANHlmxpP4hjifM=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + }, + "is-hexadecimal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz", + "integrity": "sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A==", + "dev": true + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", + "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", + "dev": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "is-my-ip-valid": "1.0.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", + "dev": true + }, + "is-odd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", + "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", + "dev": true, + "requires": { + "is-number": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "1.0.0" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-ssh": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.0.tgz", + "integrity": "sha1-6+oRaaJhTaOSpjdANmw84EnY3/Y=", + "dev": true, + "requires": { + "protocols": "1.4.6" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-whitespace-character": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz", + "integrity": "sha512-SzM+T5GKUCtLhlHFKt2SDAX2RFzfS6joT91F2/WSi9LxgFdsnhfPK/UIA+JhRR2xuyLdrCys2PiFDrtn1fU5hQ==", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-word-character": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.1.tgz", + "integrity": "sha1-WgP6HqkazopusMfNdw64bWXIvvs=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isbinaryfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", + "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "abbrev": "1.0.9", + "async": "1.5.2", + "escodegen": "1.8.1", + "esprima": "2.7.3", + "glob": "5.0.15", + "handlebars": "4.0.11", + "js-yaml": "3.6.1", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "once": "1.4.0", + "resolve": "1.1.7", + "supports-color": "3.2.3", + "which": "1.3.0", + "wordwrap": "1.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "istanbul-api": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.1.tgz", + "integrity": "sha512-duj6AlLcsWNwUpfyfHt0nWIeRiZpuShnP40YTxOGQgtaN8fd6JYSxsvxUphTDy8V5MfDXo4s/xVCIIvVCO808g==", + "dev": true, + "requires": { + "async": "2.6.0", + "compare-versions": "3.1.0", + "fileset": "2.0.3", + "istanbul-lib-coverage": "1.2.0", + "istanbul-lib-hook": "1.2.0", + "istanbul-lib-instrument": "1.10.1", + "istanbul-lib-report": "1.1.4", + "istanbul-lib-source-maps": "1.2.4", + "istanbul-reports": "1.3.0", + "js-yaml": "3.11.0", + "mkdirp": "0.5.1", + "once": "1.4.0" + }, + "dependencies": { + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "js-yaml": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.0" + } + } + } + }, + "istanbul-instrumenter-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", + "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==", + "dev": true, + "requires": { + "convert-source-map": "1.5.1", + "istanbul-lib-instrument": "1.10.1", + "loader-utils": "1.1.0", + "schema-utils": "0.3.0" + } + }, + "istanbul-lib-coverage": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", + "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.0.tgz", + "integrity": "sha512-p3En6/oGkFQV55Up8ZPC2oLxvgSxD8CzA0yBrhRZSh3pfv3OFj9aSGVC0yoerAi/O4u7jUVnOGVX1eVFM+0tmQ==", + "dev": true, + "requires": { + "append-transform": "0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz", + "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", + "dev": true, + "requires": { + "babel-generator": "6.26.1", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "istanbul-lib-coverage": "1.2.0", + "semver": "5.5.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz", + "integrity": "sha512-Azqvq5tT0U09nrncK3q82e/Zjkxa4tkFZv7E6VcqP0QCPn6oNljDPfrZEC/umNXds2t7b8sRJfs6Kmpzt8m2kA==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "1.2.0", + "mkdirp": "0.5.1", + "path-parse": "1.0.5", + "supports-color": "3.2.3" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.4.tgz", + "integrity": "sha512-UzuK0g1wyQijiaYQxj/CdNycFhAd2TLtO2obKQMTZrZ1jzEMRY3rvpASEKkaxbRR6brvdovfA03znPa/pXcejg==", + "dev": true, + "requires": { + "debug": "3.1.0", + "istanbul-lib-coverage": "1.2.0", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "source-map": "0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.3.0.tgz", + "integrity": "sha512-y2Z2IMqE1gefWUaVjrBm0mSKvUkaBy9Vqz8iwr/r40Y9hBbIteH5wqHG/9DLTfJ9xUnUT2j7A3+VVJ6EaYBllA==", + "dev": true, + "requires": { + "handlebars": "4.0.11" + } + }, + "istextorbinary": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-1.0.2.tgz", + "integrity": "sha1-rOGTVNGpoBc+/rEITOD4ewrX3s8=", + "dev": true, + "requires": { + "binaryextensions": "1.0.1", + "textextensions": "1.0.2" + } + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", + "integrity": "sha1-bl/mfYsgXOTSL60Ft3geja3MSzA=", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "2.7.3" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonfile": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", + "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "just-clone": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-1.0.2.tgz", + "integrity": "sha1-v7P672WqEqMWBYcSlFwyb9jwFDQ=" + }, + "just-extend": { + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", + "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", + "dev": true + }, + "karma": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.2.tgz", + "integrity": "sha1-TS25QChQpmVR+nhLAWT7CCTtjEs=", + "dev": true, + "requires": { + "bluebird": "3.5.1", + "body-parser": "1.18.2", + "chokidar": "1.7.0", + "colors": "1.2.1", + "combine-lists": "1.0.1", + "connect": "3.6.6", + "core-js": "2.5.5", + "di": "0.0.1", + "dom-serialize": "2.2.1", + "expand-braces": "0.1.2", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "http-proxy": "1.17.0", + "isbinaryfile": "3.0.2", + "lodash": "4.17.5", + "log4js": "2.5.3", + "mime": "1.6.0", + "minimatch": "3.0.4", + "optimist": "0.6.1", + "qjobs": "1.2.0", + "range-parser": "1.2.0", + "rimraf": "2.6.2", + "safe-buffer": "5.1.1", + "socket.io": "2.0.4", + "source-map": "0.6.1", + "tmp": "0.0.33", + "useragent": "2.2.1" + }, + "dependencies": { + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "requires": { + "micromatch": "2.3.11", + "normalize-path": "2.1.1" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "1.0.4", + "debug": "2.6.9", + "depd": "1.1.2", + "http-errors": "1.6.3", + "iconv-lite": "0.4.19", + "on-finished": "2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "1.6.16" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "1.3.2", + "async-each": "1.0.1", + "fsevents": "1.2.2", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" + } + }, + "connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "1.3.2", + "utils-merge": "1.0.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" + }, + "dependencies": { + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + } + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": "1.5.0" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.5.0" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + } + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + } + } + }, + "karma-babel-preprocessor": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/karma-babel-preprocessor/-/karma-babel-preprocessor-6.0.1.tgz", + "integrity": "sha1-euHT5klQ2+EfQht0BAqwj7WmbCE=", + "dev": true, + "requires": { + "babel-core": "6.22.0" + } + }, + "karma-browserstack-launcher": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.3.0.tgz", + "integrity": "sha512-LrPf5sU/GISkEElWyoy06J8x0c8BcOjjOwf61Wqu6M0aWQu0Eoqm9yh3xON64/ByST/CEr0GsWiREQ/EIEMd4Q==", + "dev": true, + "requires": { + "browserstack": "1.5.0", + "browserstacktunnel-wrapper": "2.0.2", + "q": "1.5.1" + }, + "dependencies": { + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + } + } + }, + "karma-chai": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", + "integrity": "sha1-vuWtQEAFF4Ea40u5RfdikJEIt5o=", + "dev": true + }, + "karma-chrome-launcher": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "dev": true, + "requires": { + "fs-access": "1.0.1", + "which": "1.3.0" + } + }, + "karma-coverage-istanbul-reporter": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-1.4.2.tgz", + "integrity": "sha512-sQHexslLF+QHzaKfK8+onTYMyvSwv+p5cDayVxhpEELGa3z0QuB+l0IMsicIkkBNMOJKQaqueiRoW7iuo7lsog==", + "dev": true, + "requires": { + "istanbul-api": "1.3.1", + "minimatch": "3.0.4" + } + }, + "karma-es5-shim": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/karma-es5-shim/-/karma-es5-shim-0.0.4.tgz", + "integrity": "sha1-zdADM8znfC5M4D46yT8vjs0fuVI=", + "dev": true, + "requires": { + "es5-shim": "4.5.10" + } + }, + "karma-firefox-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.1.0.tgz", + "integrity": "sha512-LbZ5/XlIXLeQ3cqnCbYLn+rOVhuMIK9aZwlP6eOLGzWdo1UVp7t6CN3DP4SafiRLjexKwHeKHDm0c38Mtd3VxA==", + "dev": true + }, + "karma-ie-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-ie-launcher/-/karma-ie-launcher-1.0.0.tgz", + "integrity": "sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw=", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + }, + "karma-mocha": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", + "integrity": "sha1-7qrH/8DiAetjxGdEDStpx883eL8=", + "dev": true, + "requires": { + "minimist": "1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "karma-mocha-reporter": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz", + "integrity": "sha1-FRIAlejtgZGG5HoLAS8810GJVWA=", + "dev": true, + "requires": { + "chalk": "2.4.0", + "log-symbols": "2.2.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "karma-opera-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-opera-launcher/-/karma-opera-launcher-1.0.0.tgz", + "integrity": "sha1-+lFihTGh0L6EstjcDX7iCfyP+Ro=", + "dev": true + }, + "karma-requirejs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/karma-requirejs/-/karma-requirejs-1.1.0.tgz", + "integrity": "sha1-/driy4fX68FvsCIok1ZNf+5Xh5g=", + "dev": true + }, + "karma-safari-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz", + "integrity": "sha1-lpgqLMR9BmquccVTursoMZEVos4=", + "dev": true + }, + "karma-script-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-script-launcher/-/karma-script-launcher-1.0.0.tgz", + "integrity": "sha1-zQF8TeXvCeWp2nkydhdhCN1LVC0=", + "dev": true + }, + "karma-sinon": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/karma-sinon/-/karma-sinon-1.0.5.tgz", + "integrity": "sha1-TjRD8oMP3s/2JNN0cWPxIX2qKpo=", + "dev": true + }, + "karma-sourcemap-loader": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.7.tgz", + "integrity": "sha1-kTIsd/jxPUb+0GKwQuEAnUxFBdg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "karma-spec-reporter": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.31.tgz", + "integrity": "sha1-SDDccUihVcfXoYbmMjOaDYD63sM=", + "dev": true, + "requires": { + "colors": "1.2.1" + } + }, + "karma-webpack": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-2.0.13.tgz", + "integrity": "sha512-2cyII34jfrAabbI2+4Rk4j95Nazl98FvZQhgSiqKUDarT317rxfv/EdzZ60CyATN4PQxJdO5ucR5bOOXkEVrXw==", + "dev": true, + "requires": { + "async": "2.6.0", + "babel-runtime": "6.26.0", + "loader-utils": "1.1.0", + "lodash": "4.17.5", + "source-map": "0.5.7", + "webpack-dev-middleware": "1.12.2" + }, + "dependencies": { + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "kebab-case": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.0.tgz", + "integrity": "sha1-P55JkK3K0MaGwOcB92RYaPdfkes=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "requires": { + "readable-stream": "2.3.6" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } + }, + "lcov-parse": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", + "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", + "dev": true + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "requires": { + "flush-write-stream": "1.0.3" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "libbase64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz", + "integrity": "sha1-YjUag5VjrF/1vSbxL2Dpgwu3UeY=", + "dev": true + }, + "libmime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-3.0.0.tgz", + "integrity": "sha1-UaGp50SOy9Ms2lRCFnW7IbwJPaY=", + "dev": true, + "requires": { + "iconv-lite": "0.4.15", + "libbase64": "0.1.0", + "libqp": "1.1.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", + "dev": true + } + } + }, + "libqp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz", + "integrity": "sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=", + "dev": true + }, + "liftoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", + "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", + "dev": true, + "requires": { + "extend": "3.0.1", + "findup-sync": "2.0.0", + "fined": "1.1.0", + "flagged-respawn": "1.0.0", + "is-plain-object": "2.0.4", + "object.map": "1.0.1", + "rechoir": "0.6.2", + "resolve": "1.7.1" + } + }, + "livereload-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz", + "integrity": "sha512-j1R0/FeGa64Y+NmqfZhyoVRzcFlOZ8sNlKzHjh4VvLULFACZhn68XrX5DFg2FhMvSMJmROuFxRSa560ECWKBMg==", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "4.0.0", + "pify": "3.0.0", + "strip-bom": "3.0.0" + } + }, + "loader-runner": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", + "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", + "dev": true + }, + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "requires": { + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1" + } + }, + "localtunnel": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/localtunnel/-/localtunnel-1.9.0.tgz", + "integrity": "sha512-wCIiIHJ8kKIcWkTQE3m1VRABvsH2ZuOkiOpZUofUCf6Q42v3VIZ+Q0YfX1Z4sYDRj0muiKL1bLvz1FeoxsPO0w==", + "dev": true, + "requires": { + "axios": "0.17.1", + "debug": "2.6.8", + "openurl": "1.1.1", + "yargs": "6.6.0" + }, + "dependencies": { + "axios": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz", + "integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=", + "dev": true, + "requires": { + "follow-redirects": "1.4.1", + "is-buffer": "1.1.6" + } + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "1.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "yargs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "dev": true, + "requires": { + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "y18n": "3.2.1", + "yargs-parser": "4.2.1" + } + }, + "yargs-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "dev": true, + "requires": { + "camelcase": "3.0.0" + } + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + } + }, + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", + "dev": true + }, + "lodash._arraycopy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", + "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=", + "dev": true + }, + "lodash._arrayeach": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", + "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=", + "dev": true + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._baseclone": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", + "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", + "dev": true, + "requires": { + "lodash._arraycopy": "3.0.0", + "lodash._arrayeach": "3.0.0", + "lodash._baseassign": "3.2.0", + "lodash._basefor": "3.0.3", + "lodash.isarray": "3.0.4", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._basefor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", + "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=", + "dev": true + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "lodash._escapehtmlchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", + "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", + "dev": true, + "requires": { + "lodash._htmlescapes": "2.4.1" + } + }, + "lodash._escapestringchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", + "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._htmlescapes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", + "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash._isnative": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", + "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", + "dev": true + }, + "lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", + "dev": true + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash._reunescapedhtml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", + "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", + "dev": true, + "requires": { + "lodash._htmlescapes": "2.4.1", + "lodash.keys": "2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + } + } + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, + "lodash._shimkeys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", + "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", + "dev": true, + "requires": { + "lodash._objecttypes": "2.4.1" + } + }, + "lodash._stack": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lodash._stack/-/lodash._stack-4.1.3.tgz", + "integrity": "sha1-dRqnbBuWSwR+dtFPxyoJP8teLdA=", + "dev": true + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, + "lodash.clone": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-3.0.3.tgz", + "integrity": "sha1-hGiMc9MrWpDKJWFpY/GJJSqZcEM=", + "dev": true, + "requires": { + "lodash._baseclone": "3.3.0", + "lodash._bindcallback": "3.0.1", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.defaults": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", + "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", + "dev": true, + "requires": { + "lodash._objecttypes": "2.4.1", + "lodash.keys": "2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + } + } + }, + "lodash.defaultsdeep": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.3.2.tgz", + "integrity": "sha1-bBpYbmxWR7DmTi15gUG4g2FYvoo=", + "dev": true, + "requires": { + "lodash._baseclone": "4.5.7", + "lodash._stack": "4.1.3", + "lodash.isplainobject": "4.0.6", + "lodash.keysin": "4.2.0", + "lodash.mergewith": "4.6.1", + "lodash.rest": "4.0.5" + }, + "dependencies": { + "lodash._baseclone": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-4.5.7.tgz", + "integrity": "sha1-zkKt4IOE711i+nfDD2GkbmhvhDQ=", + "dev": true + } + } + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "requires": { + "lodash._root": "3.0.1" + } + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dev": true, + "requires": { + "lodash._objecttypes": "2.4.1" + } + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.keysin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-4.2.0.tgz", + "integrity": "sha1-jMP7NcLZSsxEOhhj4C+kB5nqbyg=", + "dev": true + }, + "lodash.mergewith": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", + "dev": true + }, + "lodash.rest": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/lodash.rest/-/lodash.rest-4.0.5.tgz", + "integrity": "sha1-lU73UEkmIDjJbR/Jiyj9r58Hcqo=", + "dev": true + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", + "dev": true + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "requires": { + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0" + } + }, + "lodash.values": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", + "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", + "dev": true, + "requires": { + "lodash.keys": "2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + } + } + }, + "log-driver": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.5.tgz", + "integrity": "sha1-euTsJXMC/XkNVXyxDJcQDYV7AFY=", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "2.4.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "log4js": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.5.3.tgz", + "integrity": "sha512-YL/qpTxYtK0iWWbuKCrevDZz5lh+OjyHHD+mICqpjnYGKdNRBvPeh/1uYjkKUemT1CSO4wwLOwphWMpKAnD9kw==", + "dev": true, + "requires": { + "amqplib": "0.5.2", + "axios": "0.15.3", + "circular-json": "0.5.3", + "date-format": "1.2.0", + "debug": "3.1.0", + "hipchat-notifier": "1.1.0", + "loggly": "1.1.1", + "mailgun-js": "0.7.15", + "nodemailer": "2.7.2", + "redis": "2.8.0", + "semver": "5.5.0", + "slack-node": "0.2.0", + "streamroller": "0.7.0" + }, + "dependencies": { + "circular-json": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.3.tgz", + "integrity": "sha512-YlxLOimeIoQGHnMe3kbf8qIV2Bj7uXLbljMPRguNT49GmSAzooNfS9EJ91rSJKbLBOOzM5agvtx0WyechZN/Hw==", + "dev": true + } + } + }, + "loggly": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/loggly/-/loggly-1.1.1.tgz", + "integrity": "sha1-Cg/B0/o6XsRP3HuJe+uipGlc6+4=", + "dev": true, + "optional": true, + "requires": { + "json-stringify-safe": "5.0.1", + "request": "2.75.0", + "timespan": "2.3.0" + }, + "dependencies": { + "bl": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", + "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", + "dev": true, + "optional": true, + "requires": { + "readable-stream": "2.0.6" + } + }, + "form-data": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.0.0.tgz", + "integrity": "sha1-bwrrrcxdoWwT4ezBETfYX5uIOyU=", + "dev": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true, + "optional": true + }, + "qs": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz", + "integrity": "sha1-HPyyXBCpsrSDBT/zn138kjOQjP4=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.75.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.75.0.tgz", + "integrity": "sha1-0rgmiihtoT6qXQGt9dGMyQ9lfZM=", + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.7.0", + "bl": "1.1.2", + "caseless": "0.11.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.0.0", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "node-uuid": "1.4.8", + "oauth-sign": "0.8.2", + "qs": "6.2.3", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.4.3" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true, + "optional": true + } + } + }, + "lolex": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", + "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "longest-streak": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.2.tgz", + "integrity": "sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA==", + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true, + "requires": { + "js-tokens": "3.0.2" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "0.4.1", + "signal-exit": "3.0.2" + } + }, + "lru-cache": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", + "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "requires": { + "es5-ext": "0.10.42" + } + }, + "magic-string": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.16.0.tgz", + "integrity": "sha1-lw67DacZMwEoX7GqZQ85vdgetFo=", + "dev": true, + "requires": { + "vlq": "0.2.3" + } + }, + "mailcomposer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-4.0.1.tgz", + "integrity": "sha1-DhxEsqB890DuF9wUm6AJ8Zyt/rQ=", + "dev": true, + "optional": true, + "requires": { + "buildmail": "4.0.1", + "libmime": "3.0.0" + } + }, + "mailgun-js": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/mailgun-js/-/mailgun-js-0.7.15.tgz", + "integrity": "sha1-7jZqINrGTDwVwD1sGz4O15UlKrs=", + "dev": true, + "optional": true, + "requires": { + "async": "2.1.5", + "debug": "2.2.0", + "form-data": "2.1.4", + "inflection": "1.10.0", + "is-stream": "1.1.0", + "path-proxy": "1.0.0", + "proxy-agent": "2.0.0", + "q": "1.4.1", + "tsscmp": "1.0.5" + }, + "dependencies": { + "async": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz", + "integrity": "sha1-5YfGhYCZSsZ/xW/4bTrFa9voELw=", + "dev": true, + "optional": true, + "requires": { + "lodash": "4.17.5" + } + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "optional": true, + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true, + "optional": true + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true, + "optional": true + } + } + }, + "make-dir": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz", + "integrity": "sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==", + "dev": true, + "requires": { + "pify": "3.0.0" + } + }, + "make-error": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", + "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "dev": true + }, + "make-error-cause": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz", + "integrity": "sha1-3wOI/NCzeBbf8KX7gQiTl3fcvJ0=", + "dev": true, + "requires": { + "make-error": "1.3.4" + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "1.0.1" + } + }, + "markdown-escapes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.2.tgz", + "integrity": "sha512-lbRZ2mE3Q9RtLjxZBZ9+IMl68DKIXaVAhwvwn9pmjnPLS0h/6kyBMgNhqi1xFJ/2yv6cSyv0jbiZavZv93JkkA==", + "dev": true + }, + "markdown-table": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.2.tgz", + "integrity": "sha512-NcWuJFHDA8V3wkDgR/j4+gZx+YQwstPgfQDV8ndUeWWzta3dnDTBxpVzqS9lkmJAuV5YX35lmyojl6HO5JXAgw==", + "dev": true + }, + "match-stream": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/match-stream/-/match-stream-0.0.2.tgz", + "integrity": "sha1-mesFAJOzTf+t5CG5rAtBCpz6F88=", + "dev": true, + "requires": { + "buffers": "0.1.1", + "readable-stream": "1.0.34" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "md5.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "dev": true, + "requires": { + "hash-base": "3.0.4", + "inherits": "2.0.3" + } + }, + "mdast-util-compact": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.1.tgz", + "integrity": "sha1-zbX4TitqLTEU3zO9BdnLMuPECDo=", + "dev": true, + "requires": { + "unist-util-modify-children": "1.1.1", + "unist-util-visit": "1.3.0" + } + }, + "mdast-util-definitions": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-1.2.2.tgz", + "integrity": "sha512-9NloPSwaB9f1PKcGqaScfqRf6zKOEjTIXVIbPOmgWI/JKxznlgVXC5C+8qgl3AjYg2vJBRgLYfLICaNiac89iA==", + "dev": true, + "requires": { + "unist-util-visit": "1.3.0" + } + }, + "mdast-util-inject": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz", + "integrity": "sha1-2wa4tYW+lZotzS+H9HK6m3VvNnU=", + "dev": true, + "requires": { + "mdast-util-to-string": "1.0.4" + } + }, + "mdast-util-to-hast": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-3.0.0.tgz", + "integrity": "sha512-zvEXH2AUevWfKuBqtEPNcDUPI8UC6yIVvgEgNi1v1dLnzb5Pfm+PZjnZn0FhW1WmHcrGMX059MAoqicEauzjcw==", + "dev": true, + "requires": { + "collapse-white-space": "1.0.4", + "detab": "2.0.1", + "mdast-util-definitions": "1.2.2", + "mdurl": "1.0.1", + "trim": "0.0.1", + "trim-lines": "1.1.1", + "unist-builder": "1.0.2", + "unist-util-generated": "1.1.1", + "unist-util-position": "3.0.0", + "unist-util-visit": "1.3.0", + "xtend": "4.0.1" + } + }, + "mdast-util-to-string": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.0.4.tgz", + "integrity": "sha1-XEVch4yTVfDB5/PotxnPWDaRrPs=", + "dev": true + }, + "mdast-util-toc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-2.0.1.tgz", + "integrity": "sha1-sdLLI7+wH4Evp7Vb/+iwqL7fbyE=", + "dev": true, + "requires": { + "github-slugger": "1.2.0", + "mdast-util-to-string": "1.0.4", + "unist-util-visit": "1.3.0" + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "1.2.0" + } + }, + "memoizee": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.12.tgz", + "integrity": "sha512-sprBu6nwxBWBvBOh5v2jcsGqiGLlL2xr2dLub3vR8dnE8YB17omwtm/0NSHl8jjNbcsJd5GMWJAnTSVe/O0Wfg==", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.42", + "es6-weak-map": "2.0.2", + "event-emitter": "0.3.5", + "is-promise": "2.1.0", + "lru-queue": "0.1.0", + "next-tick": "1.0.0", + "timers-ext": "0.1.5" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "0.1.7", + "readable-stream": "2.3.6" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.6.0", + "map-obj": "1.0.1", + "minimist": "1.2.0", + "normalize-package-data": "2.4.0", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "redent": "1.0.0", + "trim-newlines": "1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + } + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "2.3.6" + } + }, + "method-override": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-2.3.10.tgz", + "integrity": "sha1-49r41d7hDdLc59SuiNYrvud0drQ=", + "dev": true, + "requires": { + "debug": "2.6.9", + "methods": "1.1.2", + "parseurl": "1.3.2", + "vary": "1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + } + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.9", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "brorand": "1.1.0" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "requires": { + "mime-db": "1.33.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "1.0.2", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mkpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-1.0.0.tgz", + "integrity": "sha1-67Opd+evHGg65v2hK1Raa6bFhT0=", + "dev": true + }, + "mocha": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.2.5.tgz", + "integrity": "sha1-07cqT+SeyUOTU/GsiT28Qw2ZMUA=", + "dev": true, + "requires": { + "commander": "2.3.0", + "debug": "2.0.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.3", + "growl": "1.8.1", + "jade": "0.26.3", + "mkdirp": "0.5.0", + "supports-color": "1.2.1" + }, + "dependencies": { + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "debug": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.0.0.tgz", + "integrity": "sha1-ib2d9nMrUSVrxnBTQrugLtEhMe8=", + "dev": true, + "requires": { + "ms": "0.6.2" + } + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "glob": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "dev": true, + "requires": { + "graceful-fs": "2.0.3", + "inherits": "2.0.3", + "minimatch": "0.2.14" + } + }, + "graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + }, + "mkdirp": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=", + "dev": true + }, + "supports-color": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.1.tgz", + "integrity": "sha1-Eu4hUHCGzZjBBY2ewPSsR2t687I=", + "dev": true + } + } + }, + "mocha-nightwatch": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mocha-nightwatch/-/mocha-nightwatch-3.2.2.tgz", + "integrity": "sha1-kby5s73gV912d8eBJeSR5Y1mZHw=", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.5", + "glob": "7.0.5", + "growl": "1.9.2", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "dependencies": { + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "glob": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz", + "integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "mock-fs": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-3.12.1.tgz", + "integrity": "sha1-/yeCTNarJjp+sFoRUjnUHTYx9fg=", + "dev": true, + "requires": { + "rewire": "2.5.2", + "semver": "5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, + "module-deps-sortable": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/module-deps-sortable/-/module-deps-sortable-4.0.6.tgz", + "integrity": "sha1-ElGkuixEqS32mJvQKdoSGk8hCbA=", + "dev": true, + "requires": { + "JSONStream": "1.3.2", + "browser-resolve": "1.11.2", + "concat-stream": "1.5.2", + "defined": "1.0.0", + "detective": "4.7.1", + "duplexer2": "0.1.4", + "inherits": "2.0.3", + "parents": "1.0.1", + "readable-stream": "2.3.6", + "resolve": "1.7.1", + "stream-combiner2": "1.1.1", + "subarg": "1.0.0", + "through2": "2.0.3", + "xtend": "4.0.1" + }, + "dependencies": { + "concat-stream": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.0.6", + "typedarray": "0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + } + } + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", + "dev": true + }, + "morgan": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", + "integrity": "sha1-X9gYOYxoGcuiinzWZk8pL+HAu/I=", + "dev": true, + "requires": { + "basic-auth": "1.0.4", + "debug": "2.2.0", + "depd": "1.0.1", + "on-finished": "2.3.0", + "on-headers": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multiparty": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-3.3.2.tgz", + "integrity": "sha1-Nd5oBNwZZD5SSfPT473GyM4wHT8=", + "dev": true, + "requires": { + "readable-stream": "1.1.14", + "stream-counter": "0.2.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "requires": { + "duplexer2": "0.0.2" + }, + "dependencies": { + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "1.1.14" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", + "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-odd": "2.0.0", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "natives": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.3.tgz", + "integrity": "sha512-BZGSYV4YOLxzoTK73l0/s/0sH9l8SHs2ocReMH1f8JYSh5FUWu4ZrKCpJdRkWXV6HFR/pZDz7bwWOVAY07q77g==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "ncp": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz", + "integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ=", + "dev": true + }, + "negotiator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz", + "integrity": "sha1-Jp1cR2gQ7JLtvntsLygxY4T5p+g=", + "dev": true + }, + "neo-async": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz", + "integrity": "sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA==", + "dev": true + }, + "netmask": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", + "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "nightwatch": { + "version": "0.9.20", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-0.9.20.tgz", + "integrity": "sha1-FW0XzQWMvDH0OrGOkV9+wpf7U+A=", + "dev": true, + "requires": { + "chai-nightwatch": "0.1.1", + "ejs": "2.5.7", + "lodash.clone": "3.0.3", + "lodash.defaultsdeep": "4.3.2", + "minimatch": "3.0.3", + "mkpath": "1.0.0", + "mocha-nightwatch": "3.2.2", + "optimist": "0.6.1", + "proxy-agent": "2.0.0", + "q": "1.4.1" + }, + "dependencies": { + "ejs": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz", + "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=", + "dev": true + }, + "minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + } + } + }, + "nise": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.3.3.tgz", + "integrity": "sha512-v1J/FLUB9PfGqZLGDBhQqODkbLotP0WtLo9R4EJY2PPu5f5Xg4o0rA8FDlmrjFSv9vBBKcfnOSpfYYuu5RTHqg==", + "dev": true, + "requires": { + "@sinonjs/formatio": "2.0.0", + "just-extend": "1.1.27", + "lolex": "2.3.2", + "path-to-regexp": "1.7.0", + "text-encoding": "0.6.4" + } + }, + "node-int64": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.3.3.tgz", + "integrity": "sha1-LW5rLs5d6FiLQ9iNG8QbJs0fqE0=", + "dev": true + }, + "node-libs-browser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", + "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "dev": true, + "requires": { + "assert": "1.4.1", + "browserify-zlib": "0.2.0", + "buffer": "4.9.1", + "console-browserify": "1.1.0", + "constants-browserify": "1.0.0", + "crypto-browserify": "3.12.0", + "domain-browser": "1.2.0", + "events": "1.1.1", + "https-browserify": "1.0.0", + "os-browserify": "0.3.0", + "path-browserify": "0.0.0", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "2.3.6", + "stream-browserify": "2.0.1", + "stream-http": "2.8.1", + "string_decoder": "1.1.1", + "timers-browserify": "2.0.10", + "tty-browserify": "0.0.0", + "url": "0.11.0", + "util": "0.10.3", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + } + } + }, + "nodemailer": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-2.7.2.tgz", + "integrity": "sha1-8kLmSa7q45tsftdA73sGHEBNMPk=", + "dev": true, + "optional": true, + "requires": { + "libmime": "3.0.0", + "mailcomposer": "4.0.1", + "nodemailer-direct-transport": "3.3.2", + "nodemailer-shared": "1.1.0", + "nodemailer-smtp-pool": "2.8.2", + "nodemailer-smtp-transport": "2.7.2", + "socks": "1.1.9" + }, + "dependencies": { + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true, + "optional": true + }, + "socks": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.9.tgz", + "integrity": "sha1-Yo1+TQSRJDVEWsC25Fk3bLPm1pE=", + "dev": true, + "optional": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "1.1.15" + } + } + } + }, + "nodemailer-direct-transport": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/nodemailer-direct-transport/-/nodemailer-direct-transport-3.3.2.tgz", + "integrity": "sha1-6W+vuQNYVglH5WkBfZfmBzilCoY=", + "dev": true, + "optional": true, + "requires": { + "nodemailer-shared": "1.1.0", + "smtp-connection": "2.12.0" + } + }, + "nodemailer-fetch": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz", + "integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=", + "dev": true + }, + "nodemailer-shared": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz", + "integrity": "sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=", + "dev": true, + "requires": { + "nodemailer-fetch": "1.6.0" + } + }, + "nodemailer-smtp-pool": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/nodemailer-smtp-pool/-/nodemailer-smtp-pool-2.8.2.tgz", + "integrity": "sha1-LrlNbPhXgLG0clzoU7nL1ejajHI=", + "dev": true, + "optional": true, + "requires": { + "nodemailer-shared": "1.1.0", + "nodemailer-wellknown": "0.1.10", + "smtp-connection": "2.12.0" + } + }, + "nodemailer-smtp-transport": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.2.tgz", + "integrity": "sha1-A9ccdjFPFKx9vHvwM6am0W1n+3c=", + "dev": true, + "optional": true, + "requires": { + "nodemailer-shared": "1.1.0", + "nodemailer-wellknown": "0.1.10", + "smtp-connection": "2.12.0" + } + }, + "nodemailer-wellknown": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz", + "integrity": "sha1-WG24EB2zDLRDjrVGc3pBqtDPE9U=", + "dev": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1.0.9" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "2.6.0", + "is-builtin-module": "1.0.0", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.3" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "now-and-later": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.0.tgz", + "integrity": "sha1-vGHLtFbXnLMiB85HygUTb/Ln1u4=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, + "null-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "object-keys": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "function-bind": "1.1.1", + "has-symbols": "1.0.0", + "object-keys": "1.0.11" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "1.0.1", + "array-slice": "1.1.0", + "for-own": "1.0.0", + "isobject": "3.0.1" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "1.0.0", + "make-iterator": "1.0.1" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + }, + "dependencies": { + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + } + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "1.2.0" + } + }, + "open": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz", + "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=", + "dev": true + }, + "openurl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz", + "integrity": "sha1-OHW0sO96UsFW8NtB1GCduw+Us4c=", + "dev": true + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.3" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optimize-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/optimize-js/-/optimize-js-1.0.3.tgz", + "integrity": "sha1-QyavhlfEpf8y2vcmYxdU9yq3/bw=", + "dev": true, + "requires": { + "acorn": "3.3.0", + "concat-stream": "1.6.2", + "estree-walker": "0.3.1", + "magic-string": "0.16.0", + "yargs": "4.8.1" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "1.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "yargs": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", + "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", + "dev": true, + "requires": { + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "lodash.assign": "4.2.0", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "window-size": "0.2.0", + "y18n": "3.2.1", + "yargs-parser": "2.4.1" + } + }, + "yargs-parser": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", + "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", + "dev": true, + "requires": { + "camelcase": "3.0.0", + "lodash.assign": "4.2.0" + } + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "orchestrator": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", + "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", + "dev": true, + "requires": { + "end-of-stream": "0.1.5", + "sequencify": "0.0.7", + "stream-consume": "0.1.1" + }, + "dependencies": { + "end-of-stream": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", + "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", + "dev": true, + "requires": { + "once": "1.3.3" + } + }, + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + } + } + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "requires": { + "readable-stream": "2.3.6" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "over": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/over/-/over-0.0.5.tgz", + "integrity": "sha1-8phS5w/X4l82DgE6jsRMgq7bVwg=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "dev": true, + "requires": { + "p-try": "1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "1.2.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pac-proxy-agent": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz", + "integrity": "sha512-QBELCWyLYPgE2Gj+4wUEiMscHrQ8nRPBzYItQNOHWavwBt25ohZHQC4qnd5IszdVVrFbLsQ+dPkm6eqdjJAmwQ==", + "dev": true, + "requires": { + "agent-base": "2.1.1", + "debug": "2.6.9", + "extend": "3.0.1", + "get-uri": "2.0.1", + "http-proxy-agent": "1.0.0", + "https-proxy-agent": "1.0.0", + "pac-resolver": "2.0.0", + "raw-body": "2.3.2", + "socks-proxy-agent": "2.1.1" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.5.0" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + } + } + }, + "pac-resolver": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-2.0.0.tgz", + "integrity": "sha1-mbiNLxk/ve78HJpSnB8yYKtSd80=", + "dev": true, + "requires": { + "co": "3.0.6", + "degenerator": "1.0.4", + "ip": "1.0.1", + "netmask": "1.0.6", + "thunkify": "2.1.2" + }, + "dependencies": { + "co": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/co/-/co-3.0.6.tgz", + "integrity": "sha1-FEXyJsXrlWE45oyawwFn6n0ua9o=", + "dev": true + } + } + }, + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "dev": true + }, + "parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "requires": { + "path-platform": "0.11.15" + } + }, + "parse-asn1": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "dev": true, + "requires": { + "asn1.js": "4.10.1", + "browserify-aes": "1.2.0", + "create-hash": "1.2.0", + "evp_bytestokey": "1.0.3", + "pbkdf2": "3.0.16" + } + }, + "parse-domain": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-domain/-/parse-domain-2.0.0.tgz", + "integrity": "sha512-09LkZIoBmYFj5Ty0oO1cjevbc42/knoiWURPUgKjJHlnK+75KDaF8+DNyEM5IYozO4Ssh6mNVOhrAKRWrwZbqQ==", + "dev": true + }, + "parse-entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.1.1.tgz", + "integrity": "sha1-gRLYhHExnyerrk1klksSL+ThuJA=", + "dev": true, + "requires": { + "character-entities": "1.2.2", + "character-entities-legacy": "1.1.2", + "character-reference-invalid": "1.1.2", + "is-alphanumerical": "1.0.2", + "is-decimal": "1.0.2", + "is-hexadecimal": "1.0.2" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "1.0.0", + "map-cache": "0.2.2", + "path-root": "0.1.1" + } + }, + "parse-git-config": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/parse-git-config/-/parse-git-config-0.2.0.tgz", + "integrity": "sha1-Jygz/dFf6hRvt10zbSNrljtv9wY=", + "dev": true, + "requires": { + "ini": "1.3.5" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + } + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "1.3.1", + "json-parse-better-errors": "1.0.2" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parse-url": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-1.3.11.tgz", + "integrity": "sha1-V8FUKKuKiSsfQ4aWRccR0OFEtVQ=", + "dev": true, + "requires": { + "is-ssh": "1.3.0", + "protocols": "1.4.6" + } + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "1.0.2" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "1.0.2" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true + }, + "path-proxy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-proxy/-/path-proxy-1.0.0.tgz", + "integrity": "sha1-GOijaFn8nS8aU7SN7hOFQ8Ag3l4=", + "dev": true, + "optional": true, + "requires": { + "inflection": "1.3.8" + }, + "dependencies": { + "inflection": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.3.8.tgz", + "integrity": "sha1-y9Fg2p91sUw8xjV41POWeEvzAU4=", + "dev": true, + "optional": true + } + } + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "0.1.2" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "3.0.0" + } + }, + "pause": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.1.0.tgz", + "integrity": "sha1-68ikqGGf8LioGsFRPDQ0/0af23Q=", + "dev": true + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "2.3.8" + } + }, + "pbkdf2": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", + "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", + "dev": true, + "requires": { + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.1", + "sha.js": "2.4.11" + } + }, + "pbkdf2-compat": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz", + "integrity": "sha1-tuDI+plJTZTgURV1gCpZpcFC8og=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "2.1.0" + } + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "1.1.0", + "arr-diff": "4.0.0", + "arr-union": "3.1.0", + "extend-shallow": "3.0.2" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "property-information": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-3.2.0.tgz", + "integrity": "sha1-/RSDyPusYYCPX+NZ52k6H0ilgzE=", + "dev": true + }, + "protocols": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.6.tgz", + "integrity": "sha1-+LsmPqG1/Xp2BNJri+Ob13Z4v4o=", + "dev": true + }, + "proxy-agent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-2.0.0.tgz", + "integrity": "sha1-V+tTR6qAXXTsaByyVknbo5yTNJk=", + "dev": true, + "requires": { + "agent-base": "2.1.1", + "debug": "2.6.9", + "extend": "3.0.1", + "http-proxy-agent": "1.0.0", + "https-proxy-agent": "1.0.0", + "lru-cache": "2.6.5", + "pac-proxy-agent": "1.1.0", + "socks-proxy-agent": "2.1.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "lru-cache": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz", + "integrity": "sha1-5W1jVBSO3o13B7WNFDIg/QjfD9U=", + "dev": true + } + } + }, + "proxyquire": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-1.8.0.tgz", + "integrity": "sha1-AtUUpb7ZhvBMuyCTrxZ0FTX3ntw=", + "dev": true, + "requires": { + "fill-keys": "1.0.2", + "module-not-found-error": "1.0.1", + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "public-encrypt": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", + "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.2.0", + "parse-asn1": "5.1.1", + "randombytes": "2.0.6" + } + }, + "pullstream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pullstream/-/pullstream-0.4.1.tgz", + "integrity": "sha1-1vs79a7Wl+gxFQ6xACwlo/iuExQ=", + "dev": true, + "requires": { + "over": "0.0.5", + "readable-stream": "1.0.34", + "setimmediate": "1.0.5", + "slice-stream": "1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + }, + "pumpify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.4.0.tgz", + "integrity": "sha512-2kmNR9ry+Pf45opRVirpNuIFotsxUGLaYqxIwuR77AYrYRMuFCz9eryHBS52L360O+NcR383CL4QYlMKPq4zYA==", + "dev": true, + "requires": { + "duplexify": "3.5.4", + "inherits": "2.0.3", + "pump": "2.0.1" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "q": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/q/-/q-1.3.0.tgz", + "integrity": "sha1-hQ15+MuDHZLhA7Rkg+TjXTRkAFA=", + "dev": true + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true + }, + "qs": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", + "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.3.tgz", + "integrity": "sha1-DJ02+/jHpPces3CFd2NXemMzW+c=", + "dev": true + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "dev": true + }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "randombytes": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "2.0.6", + "safe-buffer": "5.1.1" + } + }, + "range-parser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.3.tgz", + "integrity": "sha1-aHKCNTXGkuLCoBA4Jq/YLC4P8XU=", + "dev": true + }, + "raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", + "dev": true, + "requires": { + "bytes": "1.0.0", + "string_decoder": "0.10.31" + }, + "dependencies": { + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "4.0.0", + "normalize-package-data": "2.4.0", + "path-type": "3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "3.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.3.6", + "set-immediate-shim": "1.0.1" + } + }, + "readline2": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz", + "integrity": "sha1-mUQ7pug7gw7zBRv9fcJBqCco1Wg=", + "dev": true, + "requires": { + "mute-stream": "0.0.4", + "strip-ansi": "2.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", + "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=", + "dev": true + }, + "mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha1-qSGZYKbV1dBGWXruUSUsZlX3F34=", + "dev": true + }, + "strip-ansi": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", + "integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=", + "dev": true, + "requires": { + "ansi-regex": "1.1.1" + } + } + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "1.7.1" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "2.1.0", + "strip-indent": "1.0.1" + } + }, + "redis": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", + "dev": true, + "optional": true, + "requires": { + "double-ended-queue": "2.1.0-0", + "redis-commands": "1.3.5", + "redis-parser": "2.6.0" + } + }, + "redis-commands": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", + "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==", + "dev": true, + "optional": true + }, + "redis-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", + "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=", + "dev": true, + "optional": true + }, + "regenerate": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", + "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "private": "0.1.8" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" + } + }, + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "1.3.3", + "regjsgen": "0.2.0", + "regjsparser": "0.1.5" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remark": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/remark/-/remark-9.0.0.tgz", + "integrity": "sha512-amw8rGdD5lHbMEakiEsllmkdBP+/KpjW/PRK6NSGPZKCQowh0BT4IWXDAkRMyG3SB9dKPXWMviFjNusXzXNn3A==", + "dev": true, + "requires": { + "remark-parse": "5.0.0", + "remark-stringify": "5.0.0", + "unified": "6.1.6" + } + }, + "remark-html": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-7.0.0.tgz", + "integrity": "sha512-jqRzkZXCkM12gIY2ibMLTW41m7rfanliMTVQCFTezHJFsbH00YaTox/BX4gU+f/zCdzfhFJONtebFByvpMv37w==", + "dev": true, + "requires": { + "hast-util-sanitize": "1.1.2", + "hast-util-to-html": "3.1.0", + "mdast-util-to-hast": "3.0.0", + "xtend": "4.0.1" + } + }, + "remark-parse": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", + "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", + "dev": true, + "requires": { + "collapse-white-space": "1.0.4", + "is-alphabetical": "1.0.2", + "is-decimal": "1.0.2", + "is-whitespace-character": "1.0.2", + "is-word-character": "1.0.1", + "markdown-escapes": "1.0.2", + "parse-entities": "1.1.1", + "repeat-string": "1.6.1", + "state-toggle": "1.0.1", + "trim": "0.0.1", + "trim-trailing-lines": "1.1.0", + "unherit": "1.1.0", + "unist-util-remove-position": "1.1.1", + "vfile-location": "2.0.2", + "xtend": "4.0.1" + } + }, + "remark-reference-links": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-4.0.1.tgz", + "integrity": "sha1-AhrtHFXBh9cSs8dtAFe/UQ0wC6c=", + "dev": true, + "requires": { + "unist-util-visit": "1.3.0" + } + }, + "remark-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-slug/-/remark-slug-5.0.0.tgz", + "integrity": "sha512-bRFK90ia6iooqC5KH6e9nEIL3OwRbTPU6ed2fm/fa66uofKdmRcsmRVMwND3pXLbvH2F022cETYlE7YlVs7LNQ==", + "dev": true, + "requires": { + "github-slugger": "1.2.0", + "mdast-util-to-string": "1.0.4", + "unist-util-visit": "1.3.0" + } + }, + "remark-stringify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-5.0.0.tgz", + "integrity": "sha512-Ws5MdA69ftqQ/yhRF9XhVV29mhxbfGhbz0Rx5bQH+oJcNhhSM6nCu1EpLod+DjrFGrU0BMPs+czVmJZU7xiS7w==", + "dev": true, + "requires": { + "ccount": "1.0.3", + "is-alphanumeric": "1.0.0", + "is-decimal": "1.0.2", + "is-whitespace-character": "1.0.2", + "longest-streak": "2.0.2", + "markdown-escapes": "1.0.2", + "markdown-table": "1.1.2", + "mdast-util-compact": "1.0.1", + "parse-entities": "1.1.1", + "repeat-string": "1.6.1", + "state-toggle": "1.0.1", + "stringify-entities": "1.3.1", + "unherit": "1.1.0", + "xtend": "4.0.1" + } + }, + "remark-toc": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-5.0.0.tgz", + "integrity": "sha512-j2A/fpio1nzNRJtY6nVaFUCtXNfFPxaj6I5UHFsFgo4xKmc0VokRRIzGqz4Vfs7u+dPrHjnoHkImu1Dia0jDSQ==", + "dev": true, + "requires": { + "mdast-util-toc": "2.0.1", + "remark-slug": "5.0.0" + } + }, + "remote-origin-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/remote-origin-url/-/remote-origin-url-0.4.0.tgz", + "integrity": "sha1-TT4pAvNOLTfRwmPYdxC3frQIajA=", + "dev": true, + "requires": { + "parse-git-config": "0.2.0" + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "requires": { + "is-buffer": "1.1.6", + "is-utf8": "0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "requires": { + "remove-bom-buffer": "3.0.0", + "safe-buffer": "5.1.1", + "through2": "2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "replacestream": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-0.1.3.tgz", + "integrity": "sha1-4BjTo3ckYAzNDABZkNiiG3tU/zQ=", + "dev": true, + "requires": { + "through": "2.3.8" + } + }, + "request": { + "version": "2.79.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", + "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", + "dev": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.7.0", + "caseless": "0.11.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "qs": "6.3.2", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.4.3", + "uuid": "3.2.1" + } + }, + "requestretry": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-1.13.0.tgz", + "integrity": "sha512-Lmh9qMvnQXADGAQxsXHP4rbgO6pffCfuR8XUBdP9aitJcLQJxhp7YZK4xAVYXnPJ5E52mwrfiKQtKonPL8xsmg==", + "dev": true, + "optional": true, + "requires": { + "extend": "3.0.1", + "lodash": "4.17.5", + "request": "2.79.0", + "when": "3.7.8" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "requirejs": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.5.tgz", + "integrity": "sha512-svnO+aNcR/an9Dpi44C7KSAy5fFGLtmPbaaCeQaklUz8BQhS64tWWIIlvEA5jrWICzlO/X9KSzSeXFnZdBu8nw==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", + "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "global-modules": "1.0.0" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "requires": { + "value-or-function": "3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "response-time": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", + "integrity": "sha1-/6cbq5UtYvfB1Jt0NDVfvGjf/Fo=", + "dev": true, + "requires": { + "depd": "1.1.2", + "on-headers": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + } + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rewire": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-2.5.2.tgz", + "integrity": "sha1-ZCfee3/u+n02QBUH62SlOFvFjcc=", + "dev": true + }, + "rgb2hex": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.0.tgz", + "integrity": "sha1-zNVfhgrgxcTqN1BLlY5ELY0SMls=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "3.0.4", + "inherits": "2.0.3" + } + }, + "rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=", + "dev": true + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "2.1.0" + } + }, + "rx": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz", + "integrity": "sha1-Ia3H2A8CACr1Da6X/Z2/JIdV9WY=", + "dev": true + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "4.0.8" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "0.1.15" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "dev": true + }, + "schema-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", + "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", + "dev": true, + "requires": { + "ajv": "5.5.2" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + } + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, + "send": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.13.2.tgz", + "integrity": "sha1-dl52B8gFVFK7pvCwUllTUJhgNt4=", + "dev": true, + "requires": { + "debug": "2.2.0", + "depd": "1.1.2", + "destroy": "1.0.4", + "escape-html": "1.0.3", + "etag": "1.7.0", + "fresh": "0.3.0", + "http-errors": "1.3.1", + "mime": "1.3.4", + "ms": "0.7.1", + "on-finished": "2.3.0", + "range-parser": "1.0.3", + "statuses": "1.2.1" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", + "dev": true + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "statuses": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", + "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=", + "dev": true + } + } + }, + "sequencify": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", + "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=", + "dev": true + }, + "serve-favicon": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.3.2.tgz", + "integrity": "sha1-3UGeJo3gEqtysxnTN/IQUBP5OB8=", + "dev": true, + "requires": { + "etag": "1.7.0", + "fresh": "0.3.0", + "ms": "0.7.2", + "parseurl": "1.3.2" + }, + "dependencies": { + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "serve-index": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.7.3.tgz", + "integrity": "sha1-egV/xu4o3GP2RWbl+lexEahq7NI=", + "dev": true, + "requires": { + "accepts": "1.2.13", + "batch": "0.5.3", + "debug": "2.2.0", + "escape-html": "1.0.3", + "http-errors": "1.3.1", + "mime-types": "2.1.18", + "parseurl": "1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "serve-static": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.3.tgz", + "integrity": "sha1-zlpuzTEB/tXsCYJ9rCKpwpv7BTU=", + "dev": true, + "requires": { + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.13.2" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shelljs": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.1.tgz", + "integrity": "sha512-YA/iYtZpzFe5HyWVGrb02FjPxc4EMCfpoU/Phg9fQoyMC72u9598OUBrsU8IrtwAKG0tO8IYaqbaLIw+k3IRGA==", + "dev": true, + "requires": { + "glob": "7.1.2", + "interpret": "1.1.0", + "rechoir": "0.6.2" + } + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sinon": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", + "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", + "dev": true, + "requires": { + "@sinonjs/formatio": "2.0.0", + "diff": "3.5.0", + "lodash.get": "4.4.2", + "lolex": "2.3.2", + "nise": "1.3.3", + "supports-color": "5.4.0", + "type-detect": "4.0.8" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + } + } + }, + "slack-node": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/slack-node/-/slack-node-0.2.0.tgz", + "integrity": "sha1-3kuN3aqLeT9h29KTgQT9q/N9+jA=", + "dev": true, + "optional": true, + "requires": { + "requestretry": "1.13.0" + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "slice-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-stream/-/slice-stream-1.0.0.tgz", + "integrity": "sha1-WzO9ZvATsaf4ZGCwPUY97DmtPqA=", + "dev": true, + "requires": { + "readable-stream": "1.0.34" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "smart-buffer": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", + "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=", + "dev": true + }, + "smtp-connection": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz", + "integrity": "sha1-1275EnyyPCJZ7bHoNJwujV4tdME=", + "dev": true, + "requires": { + "httpntlm": "1.6.1", + "nodemailer-shared": "1.1.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.1", + "use": "3.1.0" + }, + "dependencies": { + "atob": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.0.tgz", + "integrity": "sha512-SuiKH8vbsOyCALjA/+EINmt/Kdl+TQPrtFgW7XZZcwtryFu9e5kQoX3bjCW6mIvGH1fbeAZZuvwGR5IlBRznGw==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", + "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", + "dev": true, + "requires": { + "atob": "2.1.0", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "socket.io": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.4.tgz", + "integrity": "sha1-waRZDO/4fs8TxyZS8Eb3FrKeYBQ=", + "dev": true, + "requires": { + "debug": "2.6.9", + "engine.io": "3.1.5", + "socket.io-adapter": "1.1.1", + "socket.io-client": "2.0.4", + "socket.io-parser": "3.1.3" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=", + "dev": true + }, + "socket.io-client": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.0.4.tgz", + "integrity": "sha1-CRilUkBtxeVAs4Dc2Xr8SmQzL44=", + "dev": true, + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "2.6.9", + "engine.io-client": "3.1.6", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "3.1.3", + "to-array": "0.1.4" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-parser": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", + "integrity": "sha512-g0a2HPqLguqAczs3dMECuA1RgoGFPyvDqcbaDEdCWY9g59kdUAz3YRmaJBNKXflrHNwB7Q12Gkf/0CZXfdHR7g==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "3.1.0", + "has-binary2": "1.0.2", + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "socks": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", + "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", + "dev": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "1.1.15" + }, + "dependencies": { + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + } + } + }, + "socks-proxy-agent": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-2.1.1.tgz", + "integrity": "sha512-sFtmYqdUK5dAMh85H0LEVFUCO7OhJJe1/z2x/Z6mxp3s7/QPf1RkZmpZy+BpuU0bEjcV9npqKjq9Y3kwFUjnxw==", + "dev": true, + "requires": { + "agent-base": "2.1.1", + "extend": "3.0.1", + "socks": "1.1.10" + } + }, + "source-list-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", + "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-resolve": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.3.1.tgz", + "integrity": "sha1-YQ9hIqRFuN1RU1oqcbeD38Ekh2E=", + "requires": { + "atob": "1.1.3", + "resolve-url": "0.2.1", + "source-map-url": "0.3.0", + "urix": "0.1.0" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.3.0.tgz", + "integrity": "sha1-fsrxO1e80J2opAxdJp2zN5nUqvk=" + }, + "space-separated-tokens": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz", + "integrity": "sha512-G3jprCEw+xFEs0ORweLmblJ3XLymGGr6hxZYTYZjIlvDti9vOBUjRQa1Rzjt012aRrocKstHwdNi+F7HguPsEA==", + "dev": true, + "requires": { + "trim": "0.0.1" + } + }, + "sparkles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", + "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", + "dev": true + }, + "spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "dev": true, + "requires": { + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "dev": true + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "requires": { + "through": "2.3.8" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "3.0.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "dev": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "state-toggle": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.1.tgz", + "integrity": "sha512-Qe8QntFrrpWTnHwvwj2FZTgv+PKIsp0B9VxLzLLbSpPXWOgRgc5LVj/aTiSfK1RqIeF9jeC1UeOH8Q8y60A7og==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "0.2.5", + "object-copy": "0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stream-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/stream-array/-/stream-array-1.1.2.tgz", + "integrity": "sha1-nl9zRfITfDDuO0mLkRToC1K7frU=", + "dev": true, + "requires": { + "readable-stream": "2.1.5" + }, + "dependencies": { + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "readable-stream": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz", + "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=", + "dev": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "requires": { + "duplexer": "0.1.1" + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "requires": { + "duplexer2": "0.1.4", + "readable-stream": "2.3.6" + } + }, + "stream-consume": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", + "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", + "dev": true + }, + "stream-counter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stream-counter/-/stream-counter-0.2.0.tgz", + "integrity": "sha1-3tJmVWMZyLDiIoErnPOyb6fZR94=", + "dev": true, + "requires": { + "readable-stream": "1.1.14" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "stream-http": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.1.tgz", + "integrity": "sha512-cQ0jo17BLca2r0GfRdZKYAGLU6JRoIWxqSOakUMuKOT6MOK7AAlE856L33QuDmAy/eeOrhLee3dZKX0Uadu93A==", + "dev": true, + "requires": { + "builtin-status-codes": "3.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "to-arraybuffer": "1.0.1", + "xtend": "4.0.1" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "streamroller": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", + "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", + "dev": true, + "requires": { + "date-format": "1.2.0", + "debug": "3.1.0", + "mkdirp": "0.5.1", + "readable-stream": "2.3.6" + } + }, + "string-replace-webpack-plugin": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/string-replace-webpack-plugin/-/string-replace-webpack-plugin-0.1.3.tgz", + "integrity": "sha1-c8ZX51nWbP6Arh4M8JGqJW0OcVw=", + "dev": true, + "requires": { + "async": "0.2.10", + "css-loader": "0.9.1", + "file-loader": "0.8.5", + "loader-utils": "0.2.17", + "style-loader": "0.8.3" + }, + "dependencies": { + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" + } + } + } + }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringify-entities": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.1.tgz", + "integrity": "sha1-sVDsLXKsTBtfMktR+2soyc3/BYw=", + "dev": true, + "requires": { + "character-entities-html4": "1.1.2", + "character-entities-legacy": "1.1.2", + "is-alphanumerical": "1.0.2", + "is-hexadecimal": "1.0.2" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "4.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "style-loader": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.8.3.tgz", + "integrity": "sha1-9Pkut9tjdodI8VBlzWcA9aHIU1c=", + "dev": true, + "optional": true, + "requires": { + "loader-utils": "0.2.17" + }, + "dependencies": { + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "optional": true, + "requires": { + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" + } + } + } + }, + "subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "requires": { + "minimist": "1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "ajv-keywords": "2.1.1", + "chalk": "2.4.0", + "lodash": "4.17.5", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "tapable": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", + "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", + "dev": true + }, + "tar-stream": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.1.5.tgz", + "integrity": "sha1-vpIYwTDCACnhB7D5Z/sj3gV50Tw=", + "dev": true, + "requires": { + "bl": "0.9.5", + "end-of-stream": "1.4.1", + "readable-stream": "1.0.34", + "xtend": "4.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "ternary-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.0.1.tgz", + "integrity": "sha1-Bk5Im0tb9gumpre8fy9cJ07Pgmk=", + "dev": true, + "requires": { + "duplexify": "3.5.4", + "fork-stream": "0.0.4", + "merge-stream": "1.0.1", + "through2": "2.0.3" + } + }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "textextensions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-1.0.2.tgz", + "integrity": "sha1-ZUhjk+4fK7A5pgy7oFsLaL2VAdI=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "requires": { + "readable-stream": "2.3.6", + "xtend": "4.0.1" + } + }, + "through2-filter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", + "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", + "dev": true, + "requires": { + "through2": "2.0.3", + "xtend": "4.0.1" + } + }, + "thunkify": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", + "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=", + "dev": true + }, + "tildify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", + "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "1.0.5" + } + }, + "timers-ext": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.5.tgz", + "integrity": "sha512-tsEStd7kmACHENhsUPaxb8Jf8/+GZZxyNFQbZD07HQOyooOa6At1rQqjffgvg7n+dxscQa9cjjMdWhJtsP2sxg==", + "requires": { + "es5-ext": "0.10.42", + "next-tick": "1.0.0" + } + }, + "timespan": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/timespan/-/timespan-2.3.0.tgz", + "integrity": "sha1-SQLOBAvRPYRcj1myfp1ZutbzmSk=", + "dev": true, + "optional": true + }, + "tiny-lr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "dev": true, + "requires": { + "body": "5.1.0", + "debug": "3.1.0", + "faye-websocket": "0.10.0", + "livereload-js": "2.3.0", + "object-assign": "4.1.1", + "qs": "6.5.1" + }, + "dependencies": { + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + } + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "requires": { + "is-absolute": "1.0.0", + "is-negated-glob": "1.0.0" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "requires": { + "through2": "2.0.3" + } + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dev": true, + "requires": { + "punycode": "1.4.1" + } + }, + "traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", + "dev": true + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", + "dev": true + }, + "trim-lines": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-1.1.1.tgz", + "integrity": "sha512-X+eloHbgJGxczUk1WSjIvn7aC9oN3jVE3rQfRVKcgpavi3jxtCn0VVKtjOBj64Yop96UYn/ujJRpTbCdAF1vyg==", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "trim-trailing-lines": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.0.tgz", + "integrity": "sha1-eu+7eAjfnWafbaLkOMrIxGradoQ=", + "dev": true + }, + "trough": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.2.tgz", + "integrity": "sha512-FHkoUZvG6Egrv9XZAyYGKEyb1JMsFphgPjoczkZC2y6W93U1jswcVURB8MUvtsahEPEVACyxD47JAL63vF4JsQ==", + "dev": true + }, + "tsscmp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz", + "integrity": "sha1-fcSjOvcVgatDN9qR2FylQn69mpc=", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true + }, + "uglifyjs-webpack-plugin": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", + "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", + "dev": true, + "requires": { + "source-map": "0.5.7", + "uglify-js": "2.8.29", + "webpack-sources": "1.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "uid-safe": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.4.tgz", + "integrity": "sha1-Otbzg2jG1MjHXsF2I/t5qh0HHYE=", + "dev": true, + "requires": { + "random-bytes": "1.0.0" + } + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + }, + "unherit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.0.tgz", + "integrity": "sha1-a5qu379z3xdWrZ4xbdmBiFhAzX0=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "xtend": "4.0.1" + } + }, + "unified": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/unified/-/unified-6.1.6.tgz", + "integrity": "sha512-pW2f82bCIo2ifuIGYcV12fL96kMMYgw7JKVEgh7ODlrM9rj6vXSY3BV+H6lCcv1ksxynFf582hwWLnA1qRFy4w==", + "dev": true, + "requires": { + "bail": "1.0.3", + "extend": "3.0.1", + "is-plain-obj": "1.1.0", + "trough": "1.0.2", + "vfile": "2.3.0", + "x-is-function": "1.0.4", + "x-is-string": "0.1.0" + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" + } + } + } + }, + "unique-stream": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", + "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", + "dev": true, + "requires": { + "json-stable-stringify": "1.0.1", + "through2-filter": "2.0.0" + } + }, + "unist-builder": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-1.0.2.tgz", + "integrity": "sha1-jDuZA+9kvPsRfdfPal2Y/Bs7J7Y=", + "dev": true, + "requires": { + "object-assign": "4.1.1" + } + }, + "unist-util-generated": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.1.tgz", + "integrity": "sha1-mfFseJWayFTe58YVwpGSTIv03n8=", + "dev": true + }, + "unist-util-is": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.1.tgz", + "integrity": "sha1-DDEmKeP5YMZukx6BLT2A53AQlHs=", + "dev": true + }, + "unist-util-modify-children": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-1.1.1.tgz", + "integrity": "sha1-ZtfmpEnm9nIguXarPLi166w55R0=", + "dev": true, + "requires": { + "array-iterate": "1.1.2" + } + }, + "unist-util-position": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.0.0.tgz", + "integrity": "sha1-5uHgPu64HF4a/lU+jUrfvXwNj4I=", + "dev": true + }, + "unist-util-remove-position": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.1.tgz", + "integrity": "sha1-WoXBVV/BugwQG4ZwfRXlD6TIcbs=", + "dev": true, + "requires": { + "unist-util-visit": "1.3.0" + } + }, + "unist-util-stringify-position": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.1.tgz", + "integrity": "sha1-PMvcU2ee7W7PN3fdf14yKcG2qjw=", + "dev": true + }, + "unist-util-visit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.3.0.tgz", + "integrity": "sha512-9ntYcxPFtl44gnwXrQKZ5bMqXMY0ZHzUpqMFiU4zcc8mmf/jzYm8GhYgezuUlX4cJIM1zIDYaO6fG/fI+L6iiQ==", + "dev": true, + "requires": { + "unist-util-is": "2.1.1" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "0.3.1", + "isobject": "3.0.1" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "unzip": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/unzip/-/unzip-0.1.11.tgz", + "integrity": "sha1-iXScY7BY19kNYZ+GuYqhU107l/A=", + "dev": true, + "requires": { + "binary": "0.3.0", + "fstream": "0.1.31", + "match-stream": "0.0.2", + "pullstream": "0.4.1", + "readable-stream": "1.0.34", + "setimmediate": "1.0.5" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "upath": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.4.tgz", + "integrity": "sha512-d4SJySNBXDaQp+DPrziv3xGS6w3d2Xt69FijJr86zMPBy23JEloMCEOUBBzuN7xCtjLCnmB9tI/z7SBCahHBOw==", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-parse": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.0.tgz", + "integrity": "sha512-ERuGxDiQ6Xw/agN4tuoCRbmwRuZP0cJ1lJxJubXr5Q/5cDa78+Dc4wfvtxzhzhkm5VvmW6Mf8EVj9SPGN4l8Lg==", + "dev": true, + "requires": { + "querystringify": "2.0.0", + "requires-port": "1.0.0" + }, + "dependencies": { + "querystringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", + "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==", + "dev": true + } + } + }, + "use": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", + "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + }, + "useragent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.2.1.tgz", + "integrity": "sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=", + "dev": true, + "requires": { + "lru-cache": "2.2.4", + "tmp": "0.0.33" + }, + "dependencies": { + "lru-cache": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", + "integrity": "sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=", + "dev": true + } + } + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=", + "dev": true + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "dev": true + }, + "uws": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", + "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==", + "dev": true, + "optional": true + }, + "v8flags": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true, + "requires": { + "user-home": "1.1.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "dev": true, + "requires": { + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" + } + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true + }, + "vary": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz", + "integrity": "sha1-meSYFWaihhGN+yuBc1ffeZM3bRA=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "vfile": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", + "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", + "dev": true, + "requires": { + "is-buffer": "1.1.6", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "1.1.1", + "vfile-message": "1.0.0" + } + }, + "vfile-location": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.2.tgz", + "integrity": "sha1-02dcWch3SY5JK0dW/2Xkrxp1IlU=", + "dev": true + }, + "vfile-message": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.0.0.tgz", + "integrity": "sha512-HPREhzTOB/sNDc9/Mxf8w0FmHnThg5CRSJdR9VRFkD2riqYWs+fuXlj5z8mIpv2LrD7uU41+oPWFOL4Mjlf+dw==", + "dev": true, + "requires": { + "unist-util-stringify-position": "1.1.1" + } + }, + "vfile-reporter": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-4.0.0.tgz", + "integrity": "sha1-6m8K4TQvSEFXOYXgX5QXNvJ96do=", + "dev": true, + "requires": { + "repeat-string": "1.6.1", + "string-width": "1.0.2", + "supports-color": "4.5.0", + "unist-util-stringify-position": "1.1.1", + "vfile-statistics": "1.1.0" + }, + "dependencies": { + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "vfile-sort": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-2.1.0.tgz", + "integrity": "sha1-SVAcnou+Wt/y6bOnZx7hseIMUhA=", + "dev": true + }, + "vfile-statistics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-1.1.0.tgz", + "integrity": "sha1-AhBMYP3u0dEbH3OtZTMLdjSz2JU=", + "dev": true + }, + "vhost": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vhost/-/vhost-3.0.2.tgz", + "integrity": "sha1-L7HezUxGaqiLD5NBrzPcGv8keNU=", + "dev": true + }, + "vinyl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", + "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "dev": true, + "requires": { + "clone": "2.1.2", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.2", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" + } + }, + "vinyl-fs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.2.tgz", + "integrity": "sha512-AUSFda1OukBwuLPBTbyuO4IRWgfXmqC4UTW0f8xrCa8Hkv9oyIU+NSqBlgfOLZRoUt7cHdo75hKQghCywpIyIw==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "1.0.0", + "glob-stream": "6.1.0", + "graceful-fs": "4.1.11", + "is-valid-glob": "1.0.0", + "lazystream": "1.0.0", + "lead": "1.0.0", + "object.assign": "4.1.0", + "pumpify": "1.4.0", + "readable-stream": "2.3.6", + "remove-bom-buffer": "3.0.0", + "remove-bom-stream": "1.2.0", + "resolve-options": "1.1.0", + "through2": "2.0.3", + "to-through": "2.0.0", + "value-or-function": "3.0.0", + "vinyl": "2.1.0", + "vinyl-sourcemap": "1.1.0" + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "requires": { + "append-buffer": "1.0.2", + "convert-source-map": "1.5.1", + "graceful-fs": "4.1.11", + "normalize-path": "2.1.1", + "now-and-later": "2.0.0", + "remove-bom-buffer": "3.0.0", + "vinyl": "2.1.0" + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "requires": { + "source-map": "0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "vlq": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", + "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", + "dev": true + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "walk": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.13.tgz", + "integrity": "sha512-78SMe7To9U7kqVhSoGho3GfspA089ZDBIj2f8jElg2hi6lUCoagtDJ8sSMFNlpAh5Ib8Jt1gQ6Y7gh9mzHtFng==", + "dev": true, + "requires": { + "foreachasync": "3.0.0" + } + }, + "watchpack": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.5.0.tgz", + "integrity": "sha512-RSlipNQB1u48cq0wH/BNfCu1tD/cJ8ydFIkNYhp9o+3d+8unClkIovpW5qpFPgmL9OE48wfAnlZydXByWP82AA==", + "dev": true, + "requires": { + "chokidar": "2.0.3", + "graceful-fs": "4.1.11", + "neo-async": "2.5.1" + } + }, + "webdriverio": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-3.4.0.tgz", + "integrity": "sha1-2dTTwxNm8FPhCvZEsOqtXoc6t7U=", + "dev": true, + "requires": { + "archiver": "0.14.4", + "array.from": "0.2.0", + "co": "4.6.0", + "css-parse": "2.0.0", + "css-value": "0.0.1", + "deepmerge": "0.2.10", + "ejs": "2.5.9", + "glob": "5.0.15", + "inquirer": "0.8.5", + "is-generator": "1.0.3", + "optimist": "0.6.1", + "q": "1.3.0", + "request": "2.49.0", + "rgb2hex": "0.1.0", + "supports-color": "1.3.1", + "url": "0.10.3", + "wgxpath": "1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", + "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=", + "dev": true + }, + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true + }, + "boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", + "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", + "dev": true, + "requires": { + "hoek": "0.9.1" + } + }, + "caseless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.8.0.tgz", + "integrity": "sha1-W8oogdQUN/VLJAfr40iIx7mtT30=", + "dev": true + }, + "cli-width": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz", + "integrity": "sha1-pNKT72frt7iNSk1CwMzwDE0eNm0=", + "dev": true + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "requires": { + "delayed-stream": "0.0.5" + } + }, + "cryptiles": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", + "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", + "dev": true, + "requires": { + "boom": "0.4.2" + } + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=", + "dev": true + }, + "form-data": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", + "dev": true, + "requires": { + "async": "0.9.2", + "combined-stream": "0.0.7", + "mime": "1.2.11" + } + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "hawk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", + "integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=", + "dev": true, + "requires": { + "boom": "0.4.2", + "cryptiles": "0.2.2", + "hoek": "0.9.1", + "sntp": "0.2.4" + } + }, + "hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=", + "dev": true + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "dev": true, + "requires": { + "asn1": "0.1.11", + "assert-plus": "0.1.5", + "ctype": "0.5.3" + } + }, + "inquirer": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz", + "integrity": "sha1-29dAz2yjtzEpamPOb22WGFHzNt8=", + "dev": true, + "requires": { + "ansi-regex": "1.1.1", + "chalk": "1.1.3", + "cli-width": "1.1.1", + "figures": "1.7.0", + "lodash": "3.10.1", + "readline2": "0.1.1", + "rx": "2.5.3", + "through": "2.3.8" + } + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=", + "dev": true + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", + "dev": true + }, + "oauth-sign": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.5.0.tgz", + "integrity": "sha1-12f1FpMlYg6rLgh+8MRy53PbZGE=", + "dev": true + }, + "qs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz", + "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=", + "dev": true + }, + "request": { + "version": "2.49.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.49.0.tgz", + "integrity": "sha1-DU9jSNwzSAWbVT5Ntg/SR43mYqc=", + "dev": true, + "requires": { + "aws-sign2": "0.5.0", + "bl": "0.9.5", + "caseless": "0.8.0", + "combined-stream": "0.0.7", + "forever-agent": "0.5.2", + "form-data": "0.1.4", + "hawk": "1.1.1", + "http-signature": "0.10.1", + "json-stringify-safe": "5.0.1", + "mime-types": "1.0.2", + "node-uuid": "1.4.8", + "oauth-sign": "0.5.0", + "qs": "2.3.3", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.4.3" + } + }, + "sntp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", + "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", + "dev": true, + "requires": { + "hoek": "0.9.1" + } + }, + "supports-color": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.3.1.tgz", + "integrity": "sha1-FXWN8J2P87SswwdTn6vicJXhBC0=", + "dev": true + } + } + }, + "webpack": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.11.0.tgz", + "integrity": "sha512-3kOFejWqj5ISpJk4Qj/V7w98h9Vl52wak3CLiw/cDOfbVTq7FeoZ0SdoHHY9PYlHr50ZS42OfvzE2vB4nncKQg==", + "dev": true, + "requires": { + "acorn": "5.5.3", + "acorn-dynamic-import": "2.0.2", + "ajv": "6.2.0", + "ajv-keywords": "3.1.0", + "async": "2.6.0", + "enhanced-resolve": "3.4.1", + "escope": "3.6.0", + "interpret": "1.1.0", + "json-loader": "0.5.7", + "json5": "0.5.1", + "loader-runner": "2.3.0", + "loader-utils": "1.1.0", + "memory-fs": "0.4.1", + "mkdirp": "0.5.1", + "node-libs-browser": "2.1.0", + "source-map": "0.5.7", + "supports-color": "4.5.0", + "tapable": "0.2.8", + "uglifyjs-webpack-plugin": "0.4.6", + "watchpack": "1.5.0", + "webpack-sources": "1.1.0", + "yargs": "8.0.2" + }, + "dependencies": { + "ajv-keywords": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.1.0.tgz", + "integrity": "sha1-rCsnk5xUPpXSwG5/f1wnvkqlQ74=", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "2.3.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "yargs": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", + "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "dev": true, + "requires": { + "camelcase": "4.1.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "read-pkg-up": "2.0.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "7.0.0" + } + } + } + }, + "webpack-core": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", + "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", + "dev": true, + "requires": { + "source-list-map": "0.1.8", + "source-map": "0.4.4" + }, + "dependencies": { + "source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "webpack-dev-middleware": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz", + "integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==", + "dev": true, + "requires": { + "memory-fs": "0.4.1", + "mime": "1.6.0", + "path-is-absolute": "1.0.1", + "range-parser": "1.0.3", + "time-stamp": "2.0.0" + }, + "dependencies": { + "time-stamp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-2.0.0.tgz", + "integrity": "sha1-lcakRTDhW6jW9KPsuMOj+sRto1c=", + "dev": true + } + } + }, + "webpack-sources": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", + "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", + "dev": true, + "requires": { + "source-list-map": "2.0.0", + "source-map": "0.6.1" + } + }, + "webpack-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/webpack-stream/-/webpack-stream-3.2.0.tgz", + "integrity": "sha1-Oh0WD7EdQXJ7fObzL3IkZPmLIYY=", + "dev": true, + "requires": { + "gulp-util": "3.0.8", + "lodash.clone": "4.5.0", + "lodash.some": "4.6.0", + "memory-fs": "0.3.0", + "through": "2.3.8", + "vinyl": "1.2.0", + "webpack": "1.15.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "requires": { + "micromatch": "2.3.11", + "normalize-path": "2.1.1" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "browserify-aes": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-0.4.0.tgz", + "integrity": "sha1-BnFJtmjfMcS1hTPgLQHoBthgjiw=", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "requires": { + "pako": "0.2.9" + } + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "1.3.2", + "async-each": "1.0.1", + "fsevents": "1.2.2", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "crypto-browserify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.3.0.tgz", + "integrity": "sha1-ufx1u0oO1h3PHNXa6W6zDJw+UGw=", + "dev": true, + "requires": { + "browserify-aes": "0.4.0", + "pbkdf2-compat": "2.0.1", + "ripemd160": "0.2.0", + "sha.js": "2.2.6" + } + }, + "enhanced-resolve": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", + "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "memory-fs": "0.2.0", + "tapable": "0.1.10" + }, + "dependencies": { + "memory-fs": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", + "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", + "dev": true + } + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "dev": true + }, + "interpret": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-0.6.6.tgz", + "integrity": "sha1-/s16GOfOXKar+5U+H4YhOknxYls=", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" + } + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", + "dev": true + }, + "memory-fs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.3.0.tgz", + "integrity": "sha1-e8xrYp46Q+hx1+Kaymrop/FcuyA=", + "dev": true, + "requires": { + "errno": "0.1.7", + "readable-stream": "2.3.6" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "node-libs-browser": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-0.7.0.tgz", + "integrity": "sha1-PicsCBnjCJNeJmdECNevDhSRuDs=", + "dev": true, + "requires": { + "assert": "1.4.1", + "browserify-zlib": "0.1.4", + "buffer": "4.9.1", + "console-browserify": "1.1.0", + "constants-browserify": "1.0.0", + "crypto-browserify": "3.3.0", + "domain-browser": "1.2.0", + "events": "1.1.1", + "https-browserify": "0.0.1", + "os-browserify": "0.2.1", + "path-browserify": "0.0.0", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "2.3.6", + "stream-browserify": "2.0.1", + "stream-http": "2.8.1", + "string_decoder": "0.10.31", + "timers-browserify": "2.0.10", + "tty-browserify": "0.0.0", + "url": "0.11.0", + "util": "0.10.3", + "vm-browserify": "0.0.4" + } + }, + "os-browserify": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz", + "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8=", + "dev": true + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "ripemd160": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-0.2.0.tgz", + "integrity": "sha1-K/GYveFnys+lHAqSjoS2i74XH84=", + "dev": true + }, + "sha.js": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.2.6.tgz", + "integrity": "sha1-F93t3F9yL7ZlAWWIlUYZd4ZzFbo=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + }, + "tapable": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", + "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", + "dev": true + }, + "uglify-js": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.7.5.tgz", + "integrity": "sha1-RhLAx7qu4rp8SH3kkErhIgefLKg=", + "dev": true, + "requires": { + "async": "0.2.10", + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + } + } + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "requires": { + "clone": "1.0.4", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" + } + }, + "watchpack": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-0.2.9.tgz", + "integrity": "sha1-Yuqkq15bo1/fwBgnVibjwPXj+ws=", + "dev": true, + "requires": { + "async": "0.9.2", + "chokidar": "1.7.0", + "graceful-fs": "4.1.11" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + } + } + }, + "webpack": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-1.15.0.tgz", + "integrity": "sha1-T/MfU9sDM55VFkqdRo7gMklo/pg=", + "dev": true, + "requires": { + "acorn": "3.3.0", + "async": "1.5.2", + "clone": "1.0.4", + "enhanced-resolve": "0.9.1", + "interpret": "0.6.6", + "loader-utils": "0.2.17", + "memory-fs": "0.3.0", + "mkdirp": "0.5.1", + "node-libs-browser": "0.7.0", + "optimist": "0.6.1", + "supports-color": "3.2.3", + "tapable": "0.1.10", + "uglify-js": "2.7.5", + "watchpack": "0.2.9", + "webpack-core": "0.6.9" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "websocket-driver": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", + "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", + "dev": true, + "requires": { + "http-parser-js": "0.4.11", + "websocket-extensions": "0.1.3" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, + "wgxpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wgxpath/-/wgxpath-1.0.0.tgz", + "integrity": "sha1-7vikudVYzEla06mit1FZfs2a9pA=", + "dev": true + }, + "when": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", + "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=", + "dev": true, + "optional": true + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "window-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "1.0.0", + "safe-buffer": "5.1.1", + "ultron": "1.1.1" + } + }, + "x-is-function": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/x-is-function/-/x-is-function-1.0.4.tgz", + "integrity": "sha1-XSlNw9Joy90GJYDgxd93o5HR+h4=", + "dev": true + }, + "x-is-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", + "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", + "dev": true + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "dev": true + }, + "xregexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", + "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.3.3.tgz", + "integrity": "sha1-BU3oth8i7v23IHBZ6u+da4P7kxo=", + "dev": true + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + }, + "zip-stream": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-0.5.2.tgz", + "integrity": "sha1-Mty8UG0Nq00hNyYlvX66rDwv/1Y=", + "dev": true, + "requires": { + "compress-commons": "0.2.9", + "lodash": "3.2.0", + "readable-stream": "1.0.34" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "lodash": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.2.0.tgz", + "integrity": "sha1-S/UKMkP5rrC6xBpV09WZBnWkYvs=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + } + } +} diff --git a/package.json b/package.json index 062e0943815..28cb310599e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.32.0-pre", + "version": "1.11.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -24,11 +24,10 @@ "node": ">=4.0" }, "devDependencies": { + "ajv": "6.2.0", "babel-core": "6.22.0", "babel-loader": "^7.1.1", - "babel-plugin-transform-es3-member-expression-literals": "6.22.0", - "babel-plugin-transform-es3-property-literals": "6.22.0", - "babel-preset-es2015": "6.22.0", + "babel-preset-env": "^1.6.1", "block-loader": "^2.1.0", "chai": "^3.3.0", "coveralls": "^2.11.11", @@ -43,16 +42,17 @@ "eslint-plugin-standard": "^3.0.1", "faker": "^3.1.0", "fs.extra": "^1.3.2", - "gulp": "^3.8.7", + "gulp": "^3.9.1", "gulp-babel": "^6.1.2", "gulp-clean": "^0.3.2", "gulp-concat": "^2.6.0", - "gulp-connect": "^5.0.0", + "gulp-connect": "5.0.0", "gulp-documentation": "^3.2.1", "gulp-eslint": "^4.0.0", "gulp-footer": "^1.0.5", "gulp-header": "^1.7.1", "gulp-if": "^2.0.2", + "gulp-js-escape": "^1.0.1", "gulp-optimize-js": "^1.1.0", "gulp-rename": "^1.2.0", "gulp-replace": "^0.4.0", @@ -64,37 +64,36 @@ "istanbul": "^0.4.5", "istanbul-instrumenter-loader": "^3.0.0", "json-loader": "^0.5.1", - "karma": "^1.7.0", + "karma": "^2.0.0", "karma-babel-preprocessor": "^6.0.1", "karma-browserstack-launcher": "^1.0.1", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^2.2.0", "karma-coverage-istanbul-reporter": "^1.3.0", "karma-es5-shim": "^0.0.4", - "karma-expect": "^1.1.0", "karma-firefox-launcher": "^1.0.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^1.3.0", + "karma-mocha-reporter": "^2.2.5", "karma-opera-launcher": "^1.0.0", "karma-requirejs": "^1.1.0", "karma-safari-launcher": "^1.0.0", - "karma-sauce-launcher": "^1.1.0", "karma-script-launcher": "^1.0.0", - "karma-sinon-ie": "^2.0.0", + "karma-sinon": "^1.0.5", "karma-sourcemap-loader": "^0.3.7", "karma-spec-reporter": "^0.0.31", "karma-webpack": "^2.0.3", "localtunnel": "^1.3.0", "lodash": "^4.17.4", "mkpath": "^1.0.0", - "mocha": "^1.21.4", + "mocha": "2.2.5", "mock-fs": "^3.11.0", "nightwatch": "^0.9.5", "open": "0.0.5", "proxyquire": "^1.7.10", "querystringify": "0.0.3", "requirejs": "^2.1.20", - "sinon": "^1.12.1", + "sinon": "^4.1.3", "string-replace-webpack-plugin": "^0.1.3", "through2": "^2.0.3", "uglify-js": "^2.8.10", @@ -106,6 +105,7 @@ "dependencies": { "babel-plugin-transform-object-assign": "^6.22.0", "core-js": "^2.4.1", - "gulp-sourcemaps": "^2.6.0" + "gulp-sourcemaps": "^2.6.0", + "just-clone": "^1.0.2" } } diff --git a/plugins/RequireEnsureWithoutJsonp.js b/plugins/RequireEnsureWithoutJsonp.js index b3b38f5e336..c15af360e56 100644 --- a/plugins/RequireEnsureWithoutJsonp.js +++ b/plugins/RequireEnsureWithoutJsonp.js @@ -15,14 +15,7 @@ function RequireEnsureWithoutJsonp() {} RequireEnsureWithoutJsonp.prototype.apply = function(compiler) { compiler.plugin('compilation', function(compilation) { compilation.mainTemplate.plugin('require-ensure', function(_, chunk, hash) { - return ( -` -if(installedChunks[chunkId] === 0) - return callback.call(null, __webpack_require__); -else - console.error('webpack chunk not found and jsonp disabled'); -` - ).trim(); + return ''; }); }); }; diff --git a/pr_review.md b/pr_review.md deleted file mode 100644 index 558b1c8bcb9..00000000000 --- a/pr_review.md +++ /dev/null @@ -1,24 +0,0 @@ -## Summary -We take PR review seriously. Please read https://medium.com/@mrjoelkemp/giving-better-code-reviews-16109e0fdd36#.xa8lc4i23 to understand how a PR review should be conducted. Be rational and strict in your review, make sure you understand exactly what the submitter's intent is. Overall 1 person should take ownership of a particular PR. When they are satisfied it's in good condition to merge, they should request 1 additional team member to review as a sanity check. Only when the PR has 2 `LGTM` from the core team should it be merged. - -### General PR review Process -- Checkout the branch (these instructions are available on the github PR page as well). -- Verify PR is a single change type. Example, refactor OR bugfix. If more than 1 type, ask submitter to break out requests. -- Verify code under review has at least 80% unit test coverage. If legacy code has no unit test coverage, ask for unit tests to be included in the PR. -- Verify tests are green in Travis-ci + local build by running `gulp serve` | `gulp test` -- Verify no code quality violations are present from jscs (should be reported in terminal) -- 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 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` notes for this submission. If no draft release is available, create one using this template https://gist.github.com/mkendall07/c3af6f4691bed8a46738b3675cb5a479 - -### New Adapter or updates to adapter process -- Follow steps above for general review process. In addition, please verify the following: -- Verify that bidder has submitted valid bid params and that bids are being received. -- Verify that bidder is not manipulating the prebid.js auction in any way or doing things that go against the principles of the project. If unsure check with the Tech Lead. -- Verify that the bidder is being as efficient as possible, ideally not loading an external library, however if they do load a library it should be cached. -- Verify that code re-use is being done properly and that changes introduced by a bidder don't impact other bidders. -- If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed. -- If the adapter is triggering any user syncs make sure they are using the user sync module in the Prebid.js core. diff --git a/src/AnalyticsAdapter.js b/src/AnalyticsAdapter.js index 3a54e1c230e..30848b341d8 100644 --- a/src/AnalyticsAdapter.js +++ b/src/AnalyticsAdapter.js @@ -5,14 +5,20 @@ import { ajax } from './ajax'; const events = require('./events'); const utils = require('./utils'); -const AUCTION_INIT = CONSTANTS.EVENTS.AUCTION_INIT; -const AUCTION_END = CONSTANTS.EVENTS.AUCTION_END; -const BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; -const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; -const BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; -const BID_WON = CONSTANTS.EVENTS.BID_WON; -const BID_ADJUSTMENT = CONSTANTS.EVENTS.BID_ADJUSTMENT; -const SET_TARGETING = CONSTANTS.EVENTS.SET_TARGETING; +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_TIMEOUT, + BID_RESPONSE, + BID_WON, + BID_ADJUSTMENT, + BIDDER_DONE, + SET_TARGETING, + AD_RENDER_FAILED + } +} = CONSTANTS; const LIBRARY = 'library'; const ENDPOINT = 'endpoint'; @@ -103,10 +109,12 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } [BID_TIMEOUT]: args => this.enqueue({ eventType: BID_TIMEOUT, args }), [BID_WON]: args => this.enqueue({ eventType: BID_WON, args }), [BID_ADJUSTMENT]: args => this.enqueue({ eventType: BID_ADJUSTMENT, args }), + [BIDDER_DONE]: args => this.enqueue({ eventType: BIDDER_DONE, args }), [SET_TARGETING]: args => this.enqueue({ eventType: SET_TARGETING, args }), [AUCTION_END]: args => this.enqueue({ eventType: AUCTION_END, args }), + [AD_RENDER_FAILED]: args => this.enqueue({ eventType: AD_RENDER_FAILED, args }), [AUCTION_INIT]: args => { - args.config = config.options; // enableAnaltyics configuration object + args.config = typeof config === 'object' ? config.options || {} : {}; // enableAnaltyics configuration object this.enqueue({ eventType: AUCTION_INIT, args }); } }; @@ -119,6 +127,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } } // finally set this function to return log message, prevents multiple adapter listeners + this._oldEnable = this.enableAnalytics; this.enableAnalytics = function _enable() { return utils.logMessage(`Analytics adapter for "${global}" already enabled, unnecessary call to \`enableAnalytics\`.`); }; @@ -128,6 +137,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } utils._each(_handlers, (handler, event) => { events.off(event, handler); }); + this.enableAnalytics = this._oldEnable ? this._oldEnable : _enable; } function _emptyQueue() { diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 2204e997084..98d9d5fb426 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -1,9 +1,13 @@ /** @module adaptermanger */ -import { flatten, getBidderCodes, getDefinedParams, shuffle } from './utils'; -import { mapSizes } from './sizeMapping'; +import { flatten, getBidderCodes, getDefinedParams, shuffle, timestamp } from './utils'; +import { resolveStatus } from './sizeMapping'; import { processNativeAdUnitParams, nativeAdapters } from './native'; import { newBidder } from './adapters/bidderFactory'; +import { ajaxBuilder } from 'src/ajax'; +import { config, RANDOM } from 'src/config'; +import includes from 'core-js/library/fn/array/includes'; +import find from 'core-js/library/fn/array/find'; var utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); @@ -12,101 +16,148 @@ let s2sTestingModule; // store s2sTesting module if it's loaded var _bidderRegistry = {}; exports.bidderRegistry = _bidderRegistry; +exports.aliasRegistry = {}; -// create s2s settings objectType_function -let _s2sConfig = { - endpoint: CONSTANTS.S2S.DEFAULT_ENDPOINT, - adapter: CONSTANTS.S2S.ADAPTER, - syncEndpoint: CONSTANTS.S2S.SYNC_ENDPOINT -}; +let _s2sConfig = {}; +config.getConfig('s2sConfig', config => { + _s2sConfig = config.s2sConfig; +}); -const RANDOM = 'random'; -const FIXED = 'fixed'; +var _analyticsRegistry = {}; -const VALID_ORDERS = {}; -VALID_ORDERS[RANDOM] = true; -VALID_ORDERS[FIXED] = true; +/** + * @typedef {object} LabelDescriptor + * @property {boolean} labelAll describes whether or not this object expects all labels to match, or any label to match + * @property {Array} labels the labels listed on the bidder or adUnit + * @property {Array} activeLabels the labels specified as being active by requestBids + */ + +/** + * Returns object describing the status of labels on the adUnit or bidder along with labels passed into requestBids + * @param bidOrAdUnit the bidder or adUnit to get label info on + * @param activeLabels the labels passed to requestBids + * @returns {LabelDescriptor} + */ +function getLabels(bidOrAdUnit, activeLabels) { + if (bidOrAdUnit.labelAll) { + return {labelAll: true, labels: bidOrAdUnit.labelAll, activeLabels}; + } + return {labelAll: false, labels: bidOrAdUnit.labelAny, activeLabels}; +} -var _analyticsRegistry = {}; -let _bidderSequence = RANDOM; - -function getBids({bidderCode, requestId, bidderRequestId, adUnits}) { - return adUnits.map(adUnit => { - return adUnit.bids.filter(bid => bid.bidder === bidderCode) - .map(bid => { - let sizes = adUnit.sizes; - if (adUnit.sizeMapping) { - let sizeMapping = mapSizes(adUnit); - if (sizeMapping === '') { - return ''; +function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels}) { + return adUnits.reduce((result, adUnit) => { + let {active, sizes: filteredAdUnitSizes} = resolveStatus(getLabels(adUnit, labels), adUnit.sizes); + + if (active) { + result.push(adUnit.bids.filter(bid => bid.bidder === bidderCode) + .reduce((bids, bid) => { + if (adUnit.mediaTypes) { + if (utils.isValidMediaTypes(adUnit.mediaTypes)) { + bid = Object.assign({}, bid, {mediaTypes: adUnit.mediaTypes}); + } else { + utils.logError( + `mediaTypes is not correctly configured for adunit ${adUnit.code}` + ); + } } - sizes = sizeMapping; - } - if (adUnit.mediaTypes) { - if (utils.isValidMediaTypes(adUnit.mediaTypes)) { - bid = Object.assign({}, bid, { mediaTypes: adUnit.mediaTypes }); - } else { - utils.logError( - `mediaTypes is not correctly configured for adunit ${adUnit.code}` - ); + const nativeParams = + adUnit.nativeParams || utils.deepAccess(adUnit, 'mediaTypes.native'); + if (nativeParams) { + bid = Object.assign({}, bid, { + nativeParams: processNativeAdUnitParams(nativeParams), + }); } - } - const nativeParams = - adUnit.nativeParams || utils.deepAccess(adUnit, 'mediaTypes.native'); - if (nativeParams) { - bid = Object.assign({}, bid, { - nativeParams: processNativeAdUnitParams(nativeParams), - }); - } - - bid = Object.assign({}, bid, getDefinedParams(adUnit, [ - 'mediaType', - 'renderer' - ])); - - return Object.assign({}, bid, { - placementCode: adUnit.code, - transactionId: adUnit.transactionId, - sizes: sizes, - bidId: bid.bid_id || utils.getUniqueIdentifierStr(), - bidderRequestId, - requestId - }); - } + bid = Object.assign({}, bid, getDefinedParams(adUnit, [ + 'mediaType', + 'renderer' + ])); + + let {active, sizes} = resolveStatus(getLabels(bid, labels), filteredAdUnitSizes); + + if (active) { + bids.push(Object.assign({}, bid, { + adUnitCode: adUnit.code, + transactionId: adUnit.transactionId, + sizes: sizes, + bidId: bid.bid_id || utils.getUniqueIdentifierStr(), + bidderRequestId, + auctionId + })); + } + return bids; + }, []) ); - }).reduce(flatten, []).filter(val => val !== ''); + } + return result; + }, []).reduce(flatten, []).filter(val => val !== ''); } -exports.callBids = ({adUnits, cbTimeout}) => { - const requestId = utils.generateUUID(); - const auctionStart = Date.now(); +function getAdUnitCopyForPrebidServer(adUnits) { + let adaptersServerSide = _s2sConfig.bidders; + let adUnitsCopy = utils.deepClone(adUnits); + + adUnitsCopy.forEach((adUnit) => { + // filter out client side bids + adUnit.bids = adUnit.bids.filter((bid) => { + return includes(adaptersServerSide, bid.bidder) && (!doingS2STesting() || bid.finalSource !== s2sTestingModule.CLIENT); + }).map((bid) => { + bid.bid_id = utils.getUniqueIdentifierStr(); + return bid; + }); + }); + + // don't send empty requests + adUnitsCopy = adUnitsCopy.filter(adUnit => { + return adUnit.bids.length !== 0; + }); + return adUnitsCopy; +} - const auctionInit = { - timestamp: auctionStart, - requestId, - timeout: cbTimeout - }; - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, auctionInit); +function getAdUnitCopyForClientAdapters(adUnits) { + let adUnitsClientCopy = utils.deepClone(adUnits); + // filter out s2s bids + adUnitsClientCopy.forEach((adUnit) => { + adUnit.bids = adUnit.bids.filter((bid) => { + return !doingS2STesting() || bid.finalSource !== s2sTestingModule.SERVER; + }) + }); - let bidderCodes = getBidderCodes(adUnits); - if (_bidderSequence === RANDOM) { - bidderCodes = shuffle(bidderCodes); + // don't send empty requests + adUnitsClientCopy = adUnitsClientCopy.filter(adUnit => { + return adUnit.bids.length !== 0; + }); + + return adUnitsClientCopy; +} + +exports.gdprDataHandler = { + consentData: null, + setConsentData: function(consentInfo) { + this.consentData = consentInfo; + }, + getConsentData: function() { + return this.consentData; } +}; - const s2sAdapter = _bidderRegistry[_s2sConfig.adapter]; - if (s2sAdapter) { - s2sAdapter.setConfig(_s2sConfig); - s2sAdapter.queueSync({bidderCodes}); +exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout, labels) { + let bidRequests = []; + + adUnits = exports.checkBidRequestSizes(adUnits); + + let bidderCodes = getBidderCodes(adUnits); + if (config.getConfig('bidderSequence') === RANDOM) { + bidderCodes = shuffle(bidderCodes); } + let clientBidderCodes = bidderCodes; let clientTestAdapters = []; - let s2sTesting = false; if (_s2sConfig.enabled) { // if s2sConfig.bidderControl testing is turned on - s2sTesting = _s2sConfig.testing && typeof s2sTestingModule !== 'undefined'; - if (s2sTesting) { + if (doingS2STesting()) { // get all adapters doing client testing clientTestAdapters = s2sTestingModule.getSourceBidderMap(adUnits)[s2sTestingModule.CLIENT]; } @@ -115,124 +166,202 @@ exports.callBids = ({adUnits, cbTimeout}) => { let adaptersServerSide = _s2sConfig.bidders; // don't call these client side (unless client request is needed for testing) - bidderCodes = bidderCodes.filter((elm) => { - return !adaptersServerSide.includes(elm) || clientTestAdapters.includes(elm); - }); - let adUnitsS2SCopy = utils.cloneJson(adUnits); - - // filter out client side bids - adUnitsS2SCopy.forEach((adUnit) => { - if (adUnit.sizeMapping) { - adUnit.sizes = mapSizes(adUnit); - delete adUnit.sizeMapping; - } - adUnit.sizes = transformHeightWidth(adUnit); - adUnit.bids = adUnit.bids.filter((bid) => { - return adaptersServerSide.includes(bid.bidder) && (!s2sTesting || bid.finalSource !== s2sTestingModule.CLIENT); - }).map((bid) => { - bid.bid_id = utils.getUniqueIdentifierStr(); - return bid; - }); - }); - - // don't send empty requests - adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnit => { - return adUnit.bids.length !== 0; + clientBidderCodes = bidderCodes.filter((elm) => { + return !includes(adaptersServerSide, elm) || includes(clientTestAdapters, elm); }); + let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits); let tid = utils.generateUUID(); adaptersServerSide.forEach(bidderCode => { const bidderRequestId = utils.getUniqueIdentifierStr(); const bidderRequest = { bidderCode, - requestId, + auctionId, bidderRequestId, tid, - bids: getBids({bidderCode, requestId, bidderRequestId, 'adUnits': adUnitsS2SCopy}), - start: new Date().getTime(), + adUnitsS2SCopy, + bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': adUnitsS2SCopy, labels}), auctionStart: auctionStart, timeout: _s2sConfig.timeout, src: CONSTANTS.S2S.SRC }; if (bidderRequest.bids.length !== 0) { - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); + bidRequests.push(bidderRequest); } }); + } - let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy}; - utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.join(',')}`); - if (s2sBidRequest.ad_units.length) { - s2sAdapter.callBids(s2sBidRequest); + // client adapters + let adUnitsClientCopy = getAdUnitCopyForClientAdapters(adUnits); + clientBidderCodes.forEach(bidderCode => { + const bidderRequestId = utils.getUniqueIdentifierStr(); + const bidderRequest = { + bidderCode, + auctionId, + bidderRequestId, + bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': adUnitsClientCopy, labels}), + auctionStart: auctionStart, + timeout: cbTimeout + }; + if (bidderRequest.bids && bidderRequest.bids.length !== 0) { + bidRequests.push(bidderRequest); } + }); + + if (exports.gdprDataHandler.getConsentData()) { + bidRequests.forEach(bidRequest => { + bidRequest['gdprConsent'] = exports.gdprDataHandler.getConsentData(); + }); } + return bidRequests; +}; - let _bidderRequests = []; - // client side adapters - let adUnitsClientCopy = utils.cloneJson(adUnits); - // filter out s2s bids - adUnitsClientCopy.forEach((adUnit) => { - adUnit.bids = adUnit.bids.filter((bid) => { - return !s2sTesting || bid.finalSource !== s2sTestingModule.SERVER; - }) - }); +exports.checkBidRequestSizes = (adUnits) => { + function isArrayOfNums(val) { + return Array.isArray(val) && val.length === 2 && utils.isInteger(val[0]) && utils.isInteger(val[1]); + } - // don't send empty requests - adUnitsClientCopy = adUnitsClientCopy.filter(adUnit => { - return adUnit.bids.length !== 0; - }); + adUnits.forEach((adUnit) => { + if (adUnit.sizes) { + utils.logWarn('Usage of adUnits.sizes will eventually be deprecated. Please define size dimensions within the corresponding area of the mediaTypes. (eg mediaTypes.banner.sizes).'); + } - bidderCodes.forEach(bidderCode => { - const adapter = _bidderRegistry[bidderCode]; - if (adapter) { - const bidderRequestId = utils.getUniqueIdentifierStr(); - const bidderRequest = { - bidderCode, - requestId, - bidderRequestId, - bids: getBids({bidderCode, requestId, bidderRequestId, 'adUnits': adUnitsClientCopy}), - auctionStart: auctionStart, - timeout: cbTimeout - }; - if (bidderRequest.bids && bidderRequest.bids.length !== 0) { - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - _bidderRequests.push(bidderRequest); + const mediaTypes = adUnit.mediaTypes; + if (mediaTypes && mediaTypes.banner) { + const banner = mediaTypes.banner; + if (banner.sizes) { + adUnit.sizes = banner.sizes; + } else { + utils.logError('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.'); + delete adUnit.mediaTypes.banner; + } + } + + if (mediaTypes && mediaTypes.video) { + const video = mediaTypes.video; + if (video.playerSize) { + if (Array.isArray(video.playerSize) && video.playerSize.length === 1 && video.playerSize.every(isArrayOfNums)) { + adUnit.sizes = video.playerSize; + } else if (isArrayOfNums(video.playerSize)) { + let newPlayerSize = []; + newPlayerSize.push(video.playerSize); + utils.logInfo(`Transforming video.playerSize from ${video.playerSize} to ${newPlayerSize} so it's in the proper format.`); + adUnit.sizes = video.playerSize = newPlayerSize; + } else { + utils.logError('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.'); + delete adUnit.mediaTypes.video.playerSize; + } + } + } + + if (mediaTypes && mediaTypes.native) { + const native = mediaTypes.native; + if (native.image && native.image.sizes && !Array.isArray(native.image.sizes)) { + utils.logError('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.'); + delete adUnit.mediaTypes.native.image.sizes; + } + if (native.image && native.image.aspect_ratios && !Array.isArray(native.image.aspect_ratios)) { + utils.logError('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.'); + delete adUnit.mediaTypes.native.image.aspect_ratios; + } + if (native.icon && native.icon.sizes && !Array.isArray(native.icon.sizes)) { + utils.logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.'); + delete adUnit.mediaTypes.native.icon.sizes; } } }); + return adUnits; +} + +exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb) => { + if (!bidRequests.length) { + utils.logWarn('callBids executed with no bidRequests. Were they filtered by labels or sizing?'); + return; + } - _bidderRequests.forEach(bidRequest => { - bidRequest.start = new Date().getTime(); + let ajax = ajaxBuilder(bidRequests[0].timeout); + + let [clientBidRequests, serverBidRequests] = bidRequests.reduce((partitions, bidRequest) => { + partitions[Number(typeof bidRequest.src !== 'undefined' && bidRequest.src === CONSTANTS.S2S.SRC)].push(bidRequest); + return partitions; + }, [[], []]); + + if (serverBidRequests.length) { + let adaptersServerSide = _s2sConfig.bidders; + const s2sAdapter = _bidderRegistry[_s2sConfig.adapter]; + let tid = serverBidRequests[0].tid; + let adUnitsS2SCopy = serverBidRequests[0].adUnitsS2SCopy; + adUnitsS2SCopy.forEach((adUnitCopy) => { + let validBids = adUnitCopy.bids.filter((bid) => { + return find(serverBidRequests, request => { + return request.bidderCode === bid.bidder && + find(request.bids, (reqBid) => reqBid.adUnitCode === adUnitCopy.code); + }); + }); + adUnitCopy.bids = validBids; + }); + + adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnitCopy => adUnitCopy.bids.length > 0); + + if (s2sAdapter) { + let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy}; + if (s2sBidRequest.ad_units.length) { + let doneCbs = serverBidRequests.map(bidRequest => { + bidRequest.start = timestamp(); + bidRequest.doneCbCallCount = 0; + return doneCb(bidRequest.bidderRequestId) + }); + + // only log adapters that actually have adUnit bids + let allBidders = s2sBidRequest.ad_units.reduce((adapters, adUnit) => { + return adapters.concat((adUnit.bids || []).reduce((adapters, bid) => { return adapters.concat(bid.bidder) }, [])); + }, []); + utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.filter(adapter => { + return includes(allBidders, adapter); + }).join(',')}`); + + // fire BID_REQUESTED event for each s2s bidRequest + serverBidRequests.forEach(bidRequest => { + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + }); + + // make bid requests + s2sAdapter.callBids( + s2sBidRequest, + serverBidRequests, + addBidResponse, + () => doneCbs.forEach(done => done()), + ajax + ); + } + } + } + + // handle client adapter requests + clientBidRequests.forEach(bidRequest => { + bidRequest.start = timestamp(); + // TODO : Do we check for bid in pool from here and skip calling adapter again ? const adapter = _bidderRegistry[bidRequest.bidderCode]; if (adapter) { - if (bidRequest.bids && bidRequest.bids.length !== 0) { - utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); - adapter.callBids(bidRequest); - } + utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + bidRequest.doneCbCallCount = 0; + let done = doneCb(bidRequest.bidderRequestId); + adapter.callBids(bidRequest, addBidResponse, done, ajax); } else { utils.logError(`Adapter trying to be called which does not exist: ${bidRequest.bidderCode} adaptermanager.callBids`); } - }) -}; - -function transformHeightWidth(adUnit) { - let sizesObj = []; - let sizes = utils.parseSizesInput(adUnit.sizes); - sizes.forEach(size => { - let heightWidth = size.split('x'); - let sizeObj = { - 'w': parseInt(heightWidth[0]), - 'h': parseInt(heightWidth[1]) - }; - sizesObj.push(sizeObj); }); - return sizesObj; +} + +function doingS2STesting() { + return _s2sConfig && _s2sConfig.enabled && _s2sConfig.testing && s2sTestingModule; } function getSupportedMediaTypes(bidderCode) { let result = []; - if (exports.videoAdapters.includes(bidderCode)) result.push('video'); - if (nativeAdapters.includes(bidderCode)) result.push('native'); + if (includes(exports.videoAdapters, bidderCode)) result.push('video'); + if (includes(nativeAdapters, bidderCode)) result.push('native'); return result; } @@ -243,10 +372,10 @@ exports.registerBidAdapter = function (bidAdaptor, bidderCode, {supportedMediaTy if (typeof bidAdaptor.callBids === 'function') { _bidderRegistry[bidderCode] = bidAdaptor; - if (supportedMediaTypes.includes('video')) { + if (includes(supportedMediaTypes, 'video')) { exports.videoAdapters.push(bidderCode); } - if (supportedMediaTypes.includes('native')) { + if (includes(supportedMediaTypes, 'native')) { nativeAdapters.push(bidderCode); } } else { @@ -276,6 +405,7 @@ exports.aliasBidAdapter = function (bidderCode, alias) { } else { let spec = bidAdaptor.getSpec(); newAdapter = newBidder(Object.assign({}, spec, { code: alias })); + exports.aliasRegistry[alias] = bidderCode; } this.registerBidAdapter(newAdapter, alias, { supportedMediaTypes @@ -319,16 +449,8 @@ exports.enableAnalytics = function (config) { }); }; -exports.setBidderSequence = function (order) { - if (VALID_ORDERS[order]) { - _bidderSequence = order; - } else { - utils.logWarn(`Invalid order: ${order}. Bidder Sequence was not set.`); - } -}; - -exports.setS2SConfig = function (config) { - _s2sConfig = config; +exports.getBidAdapter = function(bidder) { + return _bidderRegistry[bidder]; }; // the s2sTesting module is injected when it's loaded rather than being imported @@ -336,3 +458,26 @@ exports.setS2SConfig = function (config) { exports.setS2STestingModule = function (module) { s2sTestingModule = module; }; + +exports.callTimedOutBidders = function(adUnits, timedOutBidders, cbTimeout) { + timedOutBidders = timedOutBidders.map((timedOutBidder) => { + // Adding user configured params & timeout to timeout event data + timedOutBidder.params = utils.getUserConfiguredParams(adUnits, timedOutBidder.adUnitCode, timedOutBidder.bidder); + timedOutBidder.timeout = cbTimeout; + return timedOutBidder; + }); + timedOutBidders = utils.groupBy(timedOutBidders, 'bidder'); + + Object.keys(timedOutBidders).forEach((bidder) => { + try { + const adapter = _bidderRegistry[bidder]; + const spec = adapter.getSpec(); + if (spec && spec.onTimeout && typeof spec.onTimeout === 'function') { + utils.logInfo(`Invoking ${bidder}.onTimeout`); + spec.onTimeout(timedOutBidders[bidder]); + } + } catch (e) { + utils.logWarn(`Error calling onTimeout of ${bidder}`); + } + }); +} diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index e490e17c60d..958173a0965 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -1,13 +1,15 @@ import Adapter from 'src/adapter'; import adaptermanager from 'src/adaptermanager'; import { config } from 'src/config'; -import { ajax } from 'src/ajax'; -import bidmanager from 'src/bidmanager'; import bidfactory from 'src/bidfactory'; -import { STATUS } from 'src/constants'; import { userSync } from 'src/userSync'; +import { nativeBidIsValid } from 'src/native'; +import { isValidVideoBid } from 'src/video'; +import CONSTANTS from 'src/constants.json'; +import events from 'src/events'; +import includes from 'core-js/library/fn/array/includes'; -import { logWarn, logError, parseQueryStringParameters, delayExecution } from 'src/utils'; +import { logWarn, logError, parseQueryStringParameters, delayExecution, parseSizesInput, getBidderRequest } from 'src/utils'; /** * This file aims to support Adapters during the Prebid 0.x -> 1.x transition. @@ -44,10 +46,10 @@ import { logWarn, logError, parseQueryStringParameters, delayExecution } from 's * @property {function(BidRequest[], bidderRequest): ServerRequest|ServerRequest[]} buildRequests Build the request to the Server * which requests Bids for the given array of Requests. Each BidRequest in the argument array is guaranteed to have * passed the isBidRequestValid() test. - * @property {function(*, BidRequest): Bid[]} interpretResponse Given a successful response from the Server, + * @property {function(ServerResponse, BidRequest): Bid[]} interpretResponse Given a successful response from the Server, * interpret it and return the Bid objects. This function will be run inside a try/catch. * If it throws any errors, your bids will be discarded. - * @property {function(SyncOptions, Array): UserSync[]} [getUserSyncs] Given an array of all the responses + * @property {function(SyncOptions, ServerResponse[]): UserSync[]} [getUserSyncs] Given an array of all the responses * from the server, determine which user syncs should occur. The argument array will contain every element * which has been sent through to interpretResponse. The order of syncs in this array matters. The most * important ones should come first, since publishers may limit how many are dropped on their page. @@ -72,11 +74,20 @@ import { logWarn, logError, parseQueryStringParameters, delayExecution } from 's * JSON-serialized into the Request body. */ +/** + * @typedef {object} ServerResponse + * + * @property {*} body The response body. If this is legal JSON, then it will be parsed. Otherwise it'll be a + * string with the body's content. + * @property {{get: function(string): string} headers The response headers. + * Call this like `ServerResponse.headers.get("Content-Type")` + */ + /** * @typedef {object} Bid * * @property {string} requestId The specific BidRequest which this bid is aimed at. - * This should correspond to one of the + * This should match the BidRequest.bidId which this Bid targets. * @property {string} ad A URL which can be used to load this ad, if it's chosen by the publisher. * @property {string} currency The currency code for the cpm value * @property {number} cpm The bid price, in US cents per thousand impressions. @@ -107,6 +118,9 @@ import { logWarn, logError, parseQueryStringParameters, delayExecution } from 's * @property {string} url The URL which makes the sync happen. */ +// common params for all mediaTypes +const COMMON_BID_RESPONSE_KEYS = ['requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency']; + /** * Register a bidder with prebid, using the given spec. * @@ -126,6 +140,7 @@ export function registerBidder(spec) { putBidder(spec); if (Array.isArray(spec.aliases)) { spec.aliases.forEach(alias => { + adaptermanager.aliasRegistry[alias] = spec.code; putBidder(Object.assign({}, spec, { code: alias })); }); } @@ -142,61 +157,41 @@ export function newBidder(spec) { getSpec: function() { return Object.freeze(spec); }, - callBids: function(bidderRequest) { + registerSyncs, + callBids: function(bidderRequest, addBidResponse, done, ajax) { if (!Array.isArray(bidderRequest.bids)) { return; } - // callBids must add a NO_BID response for _every_ AdUnit code, in order for the auction to - // end properly. This map stores placement codes which we've made _real_ bids on. - // - // As we add _real_ bids to the bidmanager, we'll log the ad unit codes here too. Once all the real - // bids have been added, fillNoBids() can be called to add NO_BID bids for any extra ad units, which - // will end the auction. - // - // In Prebid 1.0, this will be simplified to use the `addBidResponse` and `done` callbacks. const adUnitCodesHandled = {}; function addBidWithCode(adUnitCode, bid) { adUnitCodesHandled[adUnitCode] = true; - addBid(adUnitCode, bid); - } - function fillNoBids() { - bidderRequest.bids - .map(bidRequest => bidRequest.placementCode) - .forEach(adUnitCode => { - if (adUnitCode && !adUnitCodesHandled[adUnitCode]) { - addBid(adUnitCode, newEmptyBid()); - } - }); - } - - function addBid(code, bid) { - try { - bidmanager.addBidResponse(code, bid); - } catch (err) { - logError('Error adding bid', code, err); + if (isValid(adUnitCode, bid, [bidderRequest])) { + addBidResponse(adUnitCode, bid); } } - // After all the responses have come back, fill up the "no bid" bids and + // After all the responses have come back, call done() and // register any required usersync pixels. const responses = []; - function afterAllResponses() { - fillNoBids(); - if (spec.getUserSyncs) { - let syncs = spec.getUserSyncs({ - iframeEnabled: config.getConfig('userSync.iframeEnabled'), - pixelEnabled: config.getConfig('userSync.pixelEnabled'), - }, responses); - if (syncs) { - if (!Array.isArray(syncs)) { - syncs = [syncs]; - } - syncs.forEach((sync) => { - userSync.registerSync(sync.type, spec.code, sync.url) - }); - } + function afterAllResponses(bids) { + const bidsArray = bids ? (bids[0] ? bids : [bids]) : []; + + const videoBid = bidsArray.some(bid => bid.mediaType === 'video'); + const cacheEnabled = config.getConfig('cache.url'); + + // video bids with cache enabled need to be cached first before they are considered done + if (!(videoBid && cacheEnabled)) { + done(); } + + // TODO: the code above needs to be refactored. We should always call done when we're done. if the auction + // needs to do cleanup before _it_ can be done it should handle that itself in the auction. It should _not_ + // require us, the bidders, to conditionally call done. That makes the whole done API very flaky. + // As soon as that is refactored, we can move this emit event where it should be, within the done function. + events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest); + + registerSyncs(responses, bidderRequest.gdprConsent); } const validBidRequests = bidderRequest.bids.filter(filterAndWarn); @@ -207,6 +202,10 @@ export function newBidder(spec) { const bidRequestMap = {}; validBidRequests.forEach(bid => { bidRequestMap[bid.bidId] = bid; + // Delete this once we are 1.0 + if (!bid.adUnitCode) { + bid.adUnitCode = bid.placementCode + } }); let requests = spec.buildRequests(validBidRequests, bidderRequest); @@ -218,17 +217,25 @@ export function newBidder(spec) { requests = [requests]; } - // Callbacks don't compose as nicely as Promises. We should call fillNoBids() once _all_ the + // Callbacks don't compose as nicely as Promises. We should call done() once _all_ the // Server requests have returned and been processed. Since `ajax` accepts a single callback, // we need to rig up a function which only executes after all the requests have been responded. const onResponse = delayExecution(afterAllResponses, requests.length) requests.forEach(processRequest); + function formatGetParameters(data) { + if (data) { + return `?${typeof data === 'object' ? parseQueryStringParameters(data) : data}`; + } + + return ''; + } + function processRequest(request) { switch (request.method) { case 'GET': ajax( - `${request.url}?${typeof request.data === 'object' ? parseQueryStringParameters(request.data) : request.data}`, + `${request.url}${formatGetParameters(request.data)}`, { success: onSuccess, error: onFailure @@ -262,11 +269,17 @@ export function newBidder(spec) { // If the server responds successfully, use the adapter code to unpack the Bids from it. // If the adapter code fails, no bids should be added. After all the bids have been added, make - // sure to call the `onResponse` function so that we're one step closer to calling fillNoBids(). - function onSuccess(response) { + // sure to call the `onResponse` function so that we're one step closer to calling done(). + function onSuccess(response, responseObj) { try { response = JSON.parse(response); } catch (e) { /* response might not be JSON... that's ok. */ } + + // Make response headers available for #1742. These are lazy-loaded because most adapters won't need them. + response = { + body: response, + headers: headerParser(responseObj) + }; responses.push(response); let bids; @@ -285,21 +298,27 @@ export function newBidder(spec) { addBidUsingRequestMap(bids); } } - onResponse(); + onResponse(bids); function addBidUsingRequestMap(bid) { const bidRequest = bidRequestMap[bid.requestId]; if (bidRequest) { - const prebidBid = Object.assign(bidfactory.createBid(STATUS.GOOD, bidRequest), bid); - addBidWithCode(bidRequest.placementCode, prebidBid); + const prebidBid = Object.assign(bidfactory.createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid); + addBidWithCode(bidRequest.adUnitCode, prebidBid); } else { logWarn(`Bidder ${spec.code} made bid for unknown request ID: ${bid.requestId}. Ignoring.`); } } + + function headerParser(xmlHttpResponse) { + return { + get: responseObj.getResponseHeader.bind(responseObj) + }; + } } // If the server responds with an error, there's not much we can do. Log it, and make sure to - // call onResponse() so that we're one step closer to calling fillNoBids(). + // call onResponse() so that we're one step closer to calling done(). function onFailure(err) { logError(`Server call for ${spec.code} failed: ${err}. Continuing without bids.`); onResponse(); @@ -308,6 +327,23 @@ export function newBidder(spec) { } }); + function registerSyncs(responses, gdprConsent) { + if (spec.getUserSyncs) { + let syncs = spec.getUserSyncs({ + iframeEnabled: config.getConfig('userSync.iframeEnabled'), + pixelEnabled: config.getConfig('userSync.pixelEnabled'), + }, responses, gdprConsent); + if (syncs) { + if (!Array.isArray(syncs)) { + syncs = [syncs]; + } + syncs.forEach((sync) => { + userSync.registerSync(sync.type, spec.code, sync.url) + }); + } + } + } + function filterAndWarn(bid) { if (!spec.isBidRequestValid(bid)) { logWarn(`Invalid bid sent to bidder ${spec.code}: ${JSON.stringify(bid)}`); @@ -315,11 +351,69 @@ export function newBidder(spec) { } return true; } +} + +// check that the bid has a width and height set +function validBidSize(adUnitCode, bid, bidRequests) { + if ((bid.width || bid.width === 0) && (bid.height || bid.height === 0)) { + return true; + } + + const adUnit = getBidderRequest(bidRequests, bid.bidderCode, adUnitCode); + + const sizes = adUnit && adUnit.bids && adUnit.bids[0] && adUnit.bids[0].sizes; + const parsedSizes = parseSizesInput(sizes); + + // if a banner impression has one valid size, we assign that size to any bid + // response that does not explicitly set width or height + if (parsedSizes.length === 1) { + const [ width, height ] = parsedSizes[0].split('x'); + bid.width = width; + bid.height = height; + return true; + } + + return false; +} + +// Validate the arguments sent to us by the adapter. If this returns false, the bid should be totally ignored. +export function isValid(adUnitCode, bid, bidRequests) { + function hasValidKeys() { + let bidKeys = Object.keys(bid); + return COMMON_BID_RESPONSE_KEYS.every(key => includes(bidKeys, key)); + } + + function errorMessage(msg) { + return `Invalid bid from ${bid.bidderCode}. Ignoring bid: ${msg}`; + } - function newEmptyBid() { - const bid = bidfactory.createBid(STATUS.NO_BID); - bid.code = spec.code; - bid.bidderCode = spec.code; - return bid; + if (!adUnitCode) { + logWarn('No adUnitCode was supplied to addBidResponse.'); + return false; } + + if (!bid) { + logWarn(`Some adapter tried to add an undefined bid for ${adUnitCode}.`); + return false; + } + + if (!hasValidKeys()) { + logError(errorMessage(`Bidder ${bid.bidderCode} is missing required params. Check http://prebid.org/dev-docs/bidder-adapter-1.html for list of params.`)); + return false; + } + + if (bid.mediaType === 'native' && !nativeBidIsValid(bid, bidRequests)) { + logError(errorMessage('Native bid missing some required properties.')); + return false; + } + if (bid.mediaType === 'video' && !isValidVideoBid(bid, bidRequests)) { + logError(errorMessage(`Video bid does not have required vastUrl or renderer property`)); + return false; + } + if (bid.mediaType === 'banner' && !validBidSize(adUnitCode, bid, bidRequests)) { + logError(errorMessage(`Banner bids require a width and height`)); + return false; + } + + return true; } diff --git a/src/adloader.js b/src/adloader.js index db95f27569d..e0f2ba46cff 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -1,7 +1,46 @@ -var utils = require('./utils'); -let _requestCache = {}; +import includes from 'core-js/library/fn/array/includes'; +import * as utils from './utils'; -// add a script tag to the page, used to add /jpt call to page +const _requestCache = {}; +const _vendorWhitelist = [ + 'criteo', +] + +/** + * Loads external javascript. Can only be used if external JS is approved by Prebid. See https://github.com/prebid/prebid-js-external-js-template#policy + * Each unique URL will be loaded at most 1 time. + * @param {string} url the url to load + * @param {string} moduleCode bidderCode or module code of the module requesting this resource + */ +exports.loadExternalScript = function(url, moduleCode) { + if (!moduleCode || !url) { + utils.logError('cannot load external script without url and moduleCode'); + return; + } + if (!includes(_vendorWhitelist, moduleCode)) { + utils.logError(`${moduleCode} not whitelisted for loading external JavaScript`); + return; + } + // only load each asset once + if (_requestCache[url]) { + return; + } + + utils.logWarn(`module ${moduleCode} is loading external JavaScript`); + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.async = true; + script.src = url; + + utils.insertElement(script); + _requestCache[url] = true; +}; + +/** + * + * @deprecated + * Do not use this function. Will be removed in the next release. If external resources are required, use #loadExternalScript instead. + */ exports.loadScript = function (tagSrc, callback, cacheRequest) { // var noop = () => {}; // diff --git a/src/adserver.js b/src/adserver.js index 4346dbeb4a6..cfde042e46e 100644 --- a/src/adserver.js +++ b/src/adserver.js @@ -1,12 +1,12 @@ -import {formatQS} from './url'; -import {getWinningBids} from './targeting'; +import { formatQS } from './url'; +import { targeting } from './targeting'; // Adserver parent class const AdServer = function(attr) { this.name = attr.adserver; this.code = attr.code; this.getWinningBidByCode = function() { - return getWinningBids(this.code)[0]; + return targeting.getWinningBids(this.code)[0]; }; }; @@ -31,7 +31,7 @@ exports.dfpAdserver = function (options, urlComponents) { adserver.appendQueryParams = function() { var bid = adserver.getWinningBidByCode(); if (bid) { - this.urlComponents.search.description_url = encodeURIComponent(bid.descriptionUrl); + this.urlComponents.search.description_url = encodeURIComponent(bid.vastUrl); this.urlComponents.search.cust_params = getCustomParams(bid.adserverTargeting); this.urlComponents.search.correlator = Date.now(); } diff --git a/src/ajax.js b/src/ajax.js index 44ff00a0fe0..ded2f95f8a5 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -3,7 +3,6 @@ import {parse as parseURL, format as formatURL} from './url'; var utils = require('./utils'); const XHR_DONE = 4; -let _timeout = 3000; /** * Simple IE9+ and cross-browser ajax request function @@ -14,91 +13,98 @@ let _timeout = 3000; * @param data mixed data * @param options object */ -export function setAjaxTimeout(timeout) { - _timeout = timeout; -} +export const ajax = ajaxBuilder(); -export function ajax(url, callback, data, options = {}) { - try { - let x; - let useXDomainRequest = false; - let method = options.method || (data ? 'POST' : 'GET'); +export function ajaxBuilder(timeout = 3000) { + return function(url, callback, data, options = {}) { + try { + let x; + let useXDomainRequest = false; + let method = options.method || (data ? 'POST' : 'GET'); - let callbacks = typeof callback === 'object' ? callback : { - success: function() { - utils.logMessage('xhr success'); - }, - error: function(e) { - utils.logError('xhr error', null, e); - } - }; + let callbacks = typeof callback === 'object' && callback !== null ? callback : { + success: function() { + utils.logMessage('xhr success'); + }, + error: function(e) { + utils.logError('xhr error', null, e); + } + }; - if (typeof callback === 'function') { - callbacks.success = callback; - } + if (typeof callback === 'function') { + callbacks.success = callback; + } - if (!window.XMLHttpRequest) { - useXDomainRequest = true; - } else { - x = new window.XMLHttpRequest(); - if (x.responseType === undefined) { + if (!window.XMLHttpRequest) { useXDomainRequest = true; + } else { + x = new window.XMLHttpRequest(); + if (x.responseType === undefined) { + useXDomainRequest = true; + } } - } - if (useXDomainRequest) { - x = new window.XDomainRequest(); - x.onload = function () { - callbacks.success(x.responseText, x); - }; + if (useXDomainRequest) { + x = new window.XDomainRequest(); + x.onload = function () { + callbacks.success(x.responseText, x); + }; - // http://stackoverflow.com/questions/15786966/xdomainrequest-aborts-post-on-ie-9 - x.onerror = function () { - callbacks.error('error', x); - }; - x.ontimeout = function () { - callbacks.error('timeout', x); - }; - x.onprogress = function() { - utils.logMessage('xhr onprogress'); - }; - } else { - x.onreadystatechange = function () { - if (x.readyState === XHR_DONE) { - let status = x.status; - if ((status >= 200 && status < 300) || status === 304) { - callbacks.success(x.responseText, x); - } else { - callbacks.error(x.statusText, x); + // http://stackoverflow.com/questions/15786966/xdomainrequest-aborts-post-on-ie-9 + x.onerror = function () { + callbacks.error('error', x); + }; + x.ontimeout = function () { + callbacks.error('timeout', x); + }; + x.onprogress = function() { + utils.logMessage('xhr onprogress'); + }; + } else { + x.onreadystatechange = function () { + if (x.readyState === XHR_DONE) { + let status = x.status; + if ((status >= 200 && status < 300) || status === 304) { + callbacks.success(x.responseText, x); + } else { + callbacks.error(x.statusText, x); + } } - } - }; - } + }; + x.ontimeout = function () { + utils.logError(' xhr timeout after ', x.timeout, 'ms'); + }; + } - if (method === 'GET' && data) { - let urlInfo = parseURL(url, options); - Object.assign(urlInfo.search, data); - url = formatURL(urlInfo); - } + if (method === 'GET' && data) { + let urlInfo = parseURL(url, options); + Object.assign(urlInfo.search, data); + url = formatURL(urlInfo); + } - x.open(method, url); - // IE needs timoeut to be set after open - see #1410 - x.timeout = _timeout; + x.open(method, url); + // IE needs timoeut to be set after open - see #1410 + x.timeout = timeout; - if (!useXDomainRequest) { - if (options.withCredentials) { - x.withCredentials = true; + if (!useXDomainRequest) { + if (options.withCredentials) { + x.withCredentials = true; + } + utils._each(options.customHeaders, (value, header) => { + x.setRequestHeader(header, value); + }); + if (options.preflight) { + x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + } + x.setRequestHeader('Content-Type', options.contentType || 'text/plain'); } - utils._each(options.customHeaders, (value, header) => { - x.setRequestHeader(header, value); - }); - if (options.preflight) { - x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + if (method === 'POST' && data) { + x.send(data); + } else { + x.send(); } - x.setRequestHeader('Content-Type', options.contentType || 'text/plain'); + } catch (error) { + utils.logError('xhr construction', error); } - x.send(method === 'POST' && data); - } catch (error) { - utils.logError('xhr construction', error); } } diff --git a/src/auction.js b/src/auction.js new file mode 100644 index 00000000000..8a23605bf0e --- /dev/null +++ b/src/auction.js @@ -0,0 +1,536 @@ +/** + * Module for auction instances. + * + * In Prebid 0.x, $$PREBID_GLOBAL$$ had _bidsRequested and _bidsReceived as public properties. + * Starting 1.0, Prebid will support concurrent auctions. Each auction instance will store private properties, bidsRequested and bidsReceived. + * + * AuctionManager will create instance of auction and will store all the auctions. + * + */ + +/** + * @typedef {Object} AdUnit An object containing the adUnit configuration. + * + * @property {string} code A code which will be used to uniquely identify this bidder. This should be the same + * one as is used in the call to registerBidAdapter + * @property {Array.} sizes A list of size for adUnit. + * @property {object} params Any bidder-specific params which the publisher used in their bid request. + * This is guaranteed to have passed the spec.areParamsValid() test. + */ + +/** + * @typedef {Array.} size + */ + +/** + * @typedef {Array.} AdUnitCode + */ + +/** + * @typedef {Object} BidRequest + * //TODO add all properties + */ + +/** + * @typedef {Object} BidReceived + * //TODO add all properties + */ + +/** + * @typedef {Object} Auction + * + * @property {function(): string} getAuctionStatus - returns the auction status which can be any one of 'started', 'in progress' or 'completed' + * @property {function(): AdUnit[]} getAdUnits - return the adUnits for this auction instance + * @property {function(): AdUnitCode[]} getAdUnitCodes - return the adUnitCodes for this auction instance + * @property {function(): BidRequest[]} getBidRequests - get all bid requests for this auction instance + * @property {function(): BidReceived[]} getBidsReceived - get all bid received for this auction instance + * @property {function(): void} startAuctionTimer - sets the bidsBackHandler callback and starts the timer for auction + * @property {function(): void} callBids - sends requests to all adapters for bids + */ + +import { uniques, flatten, timestamp, adUnitsFilter, delayExecution, getBidderRequest } from './utils'; +import { getPriceBucketString } from './cpmBucketManager'; +import { getNativeTargeting } from './native'; +import { getCacheUrl, store } from './videoCache'; +import { Renderer } from 'src/Renderer'; +import { config } from 'src/config'; +import { userSync } from 'src/userSync'; +import { createHook } from 'src/hook'; +import find from 'core-js/library/fn/array/find'; +import includes from 'core-js/library/fn/array/includes'; + +const { syncUsers } = userSync; +const utils = require('./utils'); +const adaptermanager = require('./adaptermanager'); +const events = require('./events'); +const CONSTANTS = require('./constants.json'); + +export const AUCTION_STARTED = 'started'; +export const AUCTION_IN_PROGRESS = 'inProgress'; +export const AUCTION_COMPLETED = 'completed'; + +// register event for bid adjustment +events.on(CONSTANTS.EVENTS.BID_ADJUSTMENT, function (bid) { + adjustBids(bid); +}); + +/** + * Creates new auction instance + * + * @param {Object} requestConfig + * @param {AdUnit} requestConfig.adUnits + * @param {AdUnitCode} requestConfig.adUnitCode + * + * @returns {Auction} auction instance + */ +export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels}) { + let _adUnits = adUnits; + let _labels = labels; + let _adUnitCodes = adUnitCodes; + let _bidderRequests = []; + let _bidsReceived = []; + let _auctionStart; + let _auctionId = utils.generateUUID(); + let _auctionStatus; + let _callback = callback; + let _timer; + let _timeout = cbTimeout; + let _winningBids = []; + + function addBidRequests(bidderRequests) { _bidderRequests = _bidderRequests.concat(bidderRequests) }; + function addBidReceived(bidsReceived) { _bidsReceived = _bidsReceived.concat(bidsReceived); } + + function startAuctionTimer() { + const timedOut = true; + const timeoutCallback = executeCallback.bind(null, timedOut); + let timer = setTimeout(timeoutCallback, _timeout); + _timer = timer; + } + + function executeCallback(timedOut, cleartimer) { + // clear timer when done calls executeCallback + if (cleartimer) { + clearTimeout(_timer); + } + + if (_callback != null) { + let timedOutBidders = []; + if (timedOut) { + utils.logMessage(`Auction ${_auctionId} timedOut`); + timedOutBidders = getTimedOutBids(_bidderRequests, _bidsReceived); + if (timedOutBidders.length) { + events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, timedOutBidders); + } + } + + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: _auctionId}); + + try { + _auctionStatus = AUCTION_COMPLETED; + const adUnitCodes = _adUnitCodes; + const bids = [_bidsReceived + .filter(adUnitsFilter.bind(this, adUnitCodes)) + .reduce(groupByPlacement, {})]; + _callback.apply($$PREBID_GLOBAL$$, bids); + } catch (e) { + utils.logError('Error executing bidsBackHandler', null, e); + } finally { + // Calling timed out bidders + if (timedOutBidders.length) { + adaptermanager.callTimedOutBidders(adUnits, timedOutBidders, _timeout); + } + // Only automatically sync if the publisher has not chosen to "enableOverride" + let userSyncConfig = config.getConfig('userSync') || {}; + if (!userSyncConfig.enableOverride) { + // Delay the auto sync by the config delay + syncUsers(userSyncConfig.syncDelay); + } + } + _callback = null; + } + } + + function done(bidRequestId) { + const innerBidRequestId = bidRequestId; + return delayExecution(function() { + const request = find(_bidderRequests, (bidRequest) => { + return innerBidRequestId === bidRequest.bidderRequestId; + }); + + // this is done for cache-enabled video bids in tryAddVideoBid, after the cache is stored + request.doneCbCallCount += 1; + bidsBackAll(); + }, 1); + } + + /** + * Execute bidBackHandler if all bidders have called done. + */ + function bidsBackAll() { + if (_bidderRequests.every((bidRequest) => bidRequest.doneCbCallCount >= 1)) { + // when all bidders have called done callback atleast once it means auction is complete + utils.logInfo(`Bids Received for Auction with id: ${_auctionId}`, _bidsReceived); + _auctionStatus = AUCTION_COMPLETED; + executeCallback(false, true); + } + } + + function callBids() { + startAuctionTimer(); + _auctionStatus = AUCTION_STARTED; + _auctionStart = Date.now(); + + const auctionInit = { + timestamp: _auctionStart, + auctionId: _auctionId, + timeout: _timeout + }; + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, auctionInit); + + let bidRequests = adaptermanager.makeBidRequests(_adUnits, _auctionStart, _auctionId, _timeout, _labels); + utils.logInfo(`Bids Requested for Auction with id: ${_auctionId}`, bidRequests); + bidRequests.forEach(bidRequest => { + addBidRequests(bidRequest); + }); + + _auctionStatus = AUCTION_IN_PROGRESS; + adaptermanager.callBids(_adUnits, bidRequests, addBidResponse.bind(this), done.bind(this)); + }; + + return { + addBidReceived, + executeCallback, + callBids, + bidsBackAll, + addWinningBid: (winningBid) => { _winningBids = _winningBids.concat(winningBid) }, + getWinningBids: () => _winningBids, + getTimeout: () => _timeout, + getAuctionId: () => _auctionId, + getAuctionStatus: () => _auctionStatus, + getAdUnits: () => _adUnits, + getAdUnitCodes: () => _adUnitCodes, + getBidRequests: () => _bidderRequests, + getBidsReceived: () => _bidsReceived, + } +} + +function doCallbacksIfTimedout(auctionInstance, bidResponse) { + if (bidResponse.timeToRespond > auctionInstance.getTimeout() + config.getConfig('timeoutBuffer')) { + auctionInstance.executeCallback(true); + } +} + +// Add a bid to the auction. +function addBidToAuction(auctionInstance, bidResponse) { + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bidResponse); + auctionInstance.addBidReceived(bidResponse); + + doCallbacksIfTimedout(auctionInstance, bidResponse); +} + +// Video bids may fail if the cache is down, or there's trouble on the network. +function tryAddVideoBid(auctionInstance, bidResponse, bidRequest) { + let addBid = true; + if (config.getConfig('cache.url')) { + if (!bidResponse.videoCacheKey) { + addBid = false; + store([bidResponse], function (error, cacheIds) { + if (error) { + utils.logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); + + doCallbacksIfTimedout(auctionInstance, bidResponse); + } else { + bidResponse.videoCacheKey = cacheIds[0].uuid; + if (!bidResponse.vastUrl) { + bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); + } + // only set this prop after the bid has been cached to avoid early ending auction early in bidsBackAll + bidRequest.doneCbCallCount += 1; + addBidToAuction(auctionInstance, bidResponse); + auctionInstance.bidsBackAll(); + } + }); + } else if (!bidResponse.vastUrl) { + utils.logError('videoCacheKey specified but not required vastUrl for video bid'); + addBid = false; + } + } + if (addBid) { + addBidToAuction(auctionInstance, bidResponse); + } +} + +export const addBidResponse = createHook('asyncSeries', function(adUnitCode, bid) { + let auctionInstance = this; + let bidRequests = auctionInstance.getBidRequests(); + let auctionId = auctionInstance.getAuctionId(); + + let bidRequest = getBidderRequest(bidRequests, bid.bidderCode, adUnitCode); + let bidResponse = getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}); + + if (bidResponse.mediaType === 'video') { + tryAddVideoBid(auctionInstance, bidResponse, bidRequest); + } else { + addBidToAuction(auctionInstance, bidResponse); + } +}, 'addBidResponse'); + +// Postprocess the bids so that all the universal properties exist, no matter which bidder they came from. +// This should be called before addBidToAuction(). +function getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}) { + const start = bidRequest.start; + + let bidObject = Object.assign({}, bid, { + auctionId, + responseTimestamp: timestamp(), + requestTimestamp: start, + cpm: parseFloat(bid.cpm) || 0, + bidder: bid.bidderCode, + adUnitCode + }); + + bidObject.timeToRespond = bidObject.responseTimestamp - bidObject.requestTimestamp; + + // Let listeners know that now is the time to adjust the bid, if they want to. + // + // CAREFUL: Publishers rely on certain bid properties to be available (like cpm), + // but others to not be set yet (like priceStrings). See #1372 and #1389. + events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bidObject); + + // a publisher-defined renderer can be used to render bids + const adUnitRenderer = + bidRequest.bids && bidRequest.bids[0] && bidRequest.bids[0].renderer; + + if (adUnitRenderer && adUnitRenderer.url) { + bidObject.renderer = Renderer.install({ url: adUnitRenderer.url }); + bidObject.renderer.setRender(adUnitRenderer.render); + } + + const priceStringsObj = getPriceBucketString( + bidObject.cpm, + config.getConfig('customPriceBucket'), + config.getConfig('currency.granularityMultiplier') + ); + bidObject.pbLg = priceStringsObj.low; + bidObject.pbMg = priceStringsObj.med; + bidObject.pbHg = priceStringsObj.high; + bidObject.pbAg = priceStringsObj.auto; + bidObject.pbDg = priceStringsObj.dense; + bidObject.pbCg = priceStringsObj.custom; + + // if there is any key value pairs to map do here + var keyValues; + if (bidObject.bidderCode && (bidObject.cpm > 0 || bidObject.dealId)) { + keyValues = getKeyValueTargetingPairs(bidObject.bidderCode, bidObject); + } + + // use any targeting provided as defaults, otherwise just set from getKeyValueTargetingPairs + bidObject.adserverTargeting = Object.assign(bidObject.adserverTargeting || {}, keyValues); + + return bidObject; +} + +export function getStandardBidderSettings() { + let granularity = config.getConfig('priceGranularity'); + let bidder_settings = $$PREBID_GLOBAL$$.bidderSettings; + if (!bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]) { + bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] = {}; + } + if (!bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { + bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = [ + { + key: 'hb_bidder', + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: 'hb_adid', + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: 'hb_pb', + val: function (bidResponse) { + if (granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { + return bidResponse.pbAg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { + return bidResponse.pbDg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { + return bidResponse.pbLg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { + return bidResponse.pbMg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { + return bidResponse.pbHg; + } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) { + return bidResponse.pbCg; + } + } + }, { + key: 'hb_size', + val: function (bidResponse) { + return bidResponse.size; + } + }, { + key: 'hb_deal', + val: function (bidResponse) { + return bidResponse.dealId; + } + }, + { + key: 'hb_source', + val: function (bidResponse) { + return bidResponse.source; + } + }, + { + key: 'hb_format', + val: function (bidResponse) { + return bidResponse.mediaType; + } + }, + ] + } + return bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]; +} + +export function getKeyValueTargetingPairs(bidderCode, custBidObj) { + if (!custBidObj) { + return {}; + } + + var keyValues = {}; + var bidder_settings = $$PREBID_GLOBAL$$.bidderSettings; + + // 1) set the keys from "standard" setting or from prebid defaults + if (bidder_settings) { + // initialize default if not set + const standardSettings = getStandardBidderSettings(); + setKeys(keyValues, standardSettings, custBidObj); + + // 2) set keys from specific bidder setting override if they exist + if (bidderCode && bidder_settings[bidderCode] && bidder_settings[bidderCode][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { + setKeys(keyValues, bidder_settings[bidderCode], custBidObj); + custBidObj.sendStandardTargeting = bidder_settings[bidderCode].sendStandardTargeting; + } + } + + // set native key value targeting + if (custBidObj['native']) { + keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj)); + } + + return keyValues; +} + +function setKeys(keyValues, bidderSettings, custBidObj) { + var targeting = bidderSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]; + custBidObj.size = custBidObj.getSize(); + + utils._each(targeting, function (kvPair) { + var key = kvPair.key; + var value = kvPair.val; + + if (keyValues[key]) { + utils.logWarn('The key: ' + key + ' is getting ovewritten'); + } + + if (utils.isFn(value)) { + try { + value = value(custBidObj); + } catch (e) { + utils.logError('bidmanager', 'ERROR', e); + } + } + + if ( + ((typeof bidderSettings.suppressEmptyKeys !== 'undefined' && bidderSettings.suppressEmptyKeys === true) || + key === 'hb_deal') && // hb_deal is suppressed automatically if not set + ( + utils.isEmptyStr(value) || + value === null || + value === undefined + ) + ) { + utils.logInfo("suppressing empty key '" + key + "' from adserver targeting"); + } else { + keyValues[key] = value; + } + }); + + return keyValues; +} + +export function adjustBids(bid) { + let code = bid.bidderCode; + let bidPriceAdjusted = bid.cpm; + let bidCpmAdjustment; + if ($$PREBID_GLOBAL$$.bidderSettings) { + if (code && $$PREBID_GLOBAL$$.bidderSettings[code] && typeof $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment === 'function') { + bidCpmAdjustment = $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment; + } else if ($$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] && typeof $$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment === 'function') { + bidCpmAdjustment = $$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment; + } + if (bidCpmAdjustment) { + try { + bidPriceAdjusted = bidCpmAdjustment(bid.cpm, Object.assign({}, bid)); + } catch (e) { + utils.logError('Error during bid adjustment', 'bidmanager.js', e); + } + } + } + + if (bidPriceAdjusted >= 0) { + bid.cpm = bidPriceAdjusted; + } +} + +/** + * groupByPlacement is a reduce function that converts an array of Bid objects + * to an object with placement codes as keys, with each key representing an object + * with an array of `Bid` objects for that placement + * @returns {*} as { [adUnitCode]: { bids: [Bid, Bid, Bid] } } + */ +function groupByPlacement(bidsByPlacement, bid) { + if (!bidsByPlacement[bid.adUnitCode]) { bidsByPlacement[bid.adUnitCode] = { bids: [] }; } + bidsByPlacement[bid.adUnitCode].bids.push(bid); + return bidsByPlacement; +} + +/** + * Returns a list of bids that we haven't received a response yet where the bidder did not call done + * @param {BidRequest[]} bidderRequests List of bids requested for auction instance + * @param {BidReceived[]} bidsReceived List of bids received for auction instance + * + * @typedef {Object} TimedOutBid + * @property {string} bidId The id representing the bid + * @property {string} bidder The string name of the bidder + * @property {string} adUnitCode The code used to uniquely identify the ad unit on the publisher's page + * @property {string} auctionId The id representing the auction + * + * @return {Array} List of bids that Prebid hasn't received a response for + */ +function getTimedOutBids(bidderRequests, bidsReceived) { + const bidRequestedWithoutDoneCodes = bidderRequests + .filter(bidderRequest => !bidderRequest.doneCbCallCount) + .map(bid => bid.bidderCode) + .filter(uniques); + + const bidReceivedCodes = bidsReceived + .map(bid => bid.bidder) + .filter(uniques); + + const timedOutBidderCodes = bidRequestedWithoutDoneCodes + .filter(bidder => !includes(bidReceivedCodes, bidder)); + + const timedOutBids = bidderRequests + .map(bid => (bid.bids || []).filter(bid => includes(timedOutBidderCodes, bid.bidder))) + .reduce(flatten, []) + .map(bid => ({ + bidId: bid.bidId, + bidder: bid.bidder, + adUnitCode: bid.adUnitCode, + auctionId: bid.auctionId, + })); + + return timedOutBids; +} diff --git a/src/auctionManager.js b/src/auctionManager.js new file mode 100644 index 00000000000..494421b7785 --- /dev/null +++ b/src/auctionManager.js @@ -0,0 +1,98 @@ +/** + * AuctionManager modules is responsible for creating auction instances. + * This module is the gateway for Prebid core to access auctions. + * It stores all created instances of auction and can be used to get consolidated values from auction. + */ + +/** + * @typedef {Object} AuctionManager + * + * @property {function(): Array} getBidsRequested - returns consolidated bid requests + * @property {function(): Array} getBidsReceived - returns consolidated bid received + * @property {function(): Array} getAdUnits - returns consolidated adUnits + * @property {function(): Array} getAdUnitCodes - returns consolidated adUnitCodes + * @property {function(): Object} createAuction - creates auction instance and stores it for future reference + * @property {function(): Object} findBidByAdId - find bid received by adId. This function will be called by $$PREBID_GLOBAL$$.renderAd + * @property {function(): Object} getStandardBidderAdServerTargeting - returns standard bidder targeting for all the adapters. Refer http://prebid.org/dev-docs/publisher-api-reference.html#module_pbjs.bidderSettings for more details + */ + +import { uniques, flatten } from './utils'; +import { newAuction, getStandardBidderSettings, AUCTION_COMPLETED } from 'src/auction'; +import find from 'core-js/library/fn/array/find'; + +const CONSTANTS = require('./constants.json'); + +/** + * Creates new instance of auctionManager. There will only be one instance of auctionManager but + * a factory is created to assist in testing. + * + * @returns {AuctionManager} auctionManagerInstance + */ +export function newAuctionManager() { + let _auctions = []; + let auctionManager = {}; + + auctionManager.addWinningBid = function(bid) { + const auction = find(_auctions, auction => auction.getAuctionId() === bid.auctionId); + if (auction) { + auction.addWinningBid(bid); + } else { + utils.logWarn(`Auction not found when adding winning bid`); + } + } + + auctionManager.getAllWinningBids = function() { + return _auctions.map(auction => auction.getWinningBids()) + .reduce(flatten, []); + } + + auctionManager.getBidsRequested = function() { + return _auctions.map(auction => auction.getBidRequests()) + .reduce(flatten, []); + }; + + auctionManager.getBidsReceived = function() { + // As of now, an old bid which is not used in auction 1 can be used in auction n. + // To prevent this, bid.ttl (time to live) will be added to this logic and bid pool will also be added + // As of now none of the adapters are sending back bid.ttl + return _auctions.map((auction) => { + if (auction.getAuctionStatus() === AUCTION_COMPLETED) { + return auction.getBidsReceived(); + } + }).reduce(flatten, []) + .filter(bid => bid); + }; + + auctionManager.getAdUnits = function() { + return _auctions.map(auction => auction.getAdUnits()) + .reduce(flatten, []); + }; + + auctionManager.getAdUnitCodes = function() { + return _auctions.map(auction => auction.getAdUnitCodes()) + .reduce(flatten, []) + .filter(uniques); + }; + + auctionManager.createAuction = function({ adUnits, adUnitCodes, callback, cbTimeout, labels }) { + const auction = newAuction({ adUnits, adUnitCodes, callback, cbTimeout, labels }); + _addAuction(auction); + return auction; + }; + + auctionManager.findBidByAdId = function(adId) { + return find(_auctions.map(auction => auction.getBidsReceived()).reduce(flatten, []), bid => bid.adId === adId); + }; + + auctionManager.getStandardBidderAdServerTargeting = function() { + return getStandardBidderSettings()[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]; + }; + + function _addAuction(auction) { + _auctions.push(auction); + } + + return auctionManager; +} + +export const auctionManager = newAuctionManager(); diff --git a/src/bidfactory.js b/src/bidfactory.js index ff57abb8a39..6250969d6df 100644 --- a/src/bidfactory.js +++ b/src/bidfactory.js @@ -16,6 +16,7 @@ var utils = require('./utils.js'); */ function Bid(statusCode, bidRequest) { var _bidId = (bidRequest && bidRequest.bidId) || utils.getUniqueIdentifierStr(); + var _bidSrc = (bidRequest && bidRequest.src) || 'client'; var _statusCode = statusCode || 0; this.bidderCode = (bidRequest && bidRequest.bidder) || ''; @@ -24,6 +25,7 @@ function Bid(statusCode, bidRequest) { this.statusMessage = _getStatus(); this.adId = _bidId; this.mediaType = 'banner'; + this.source = _bidSrc; function _getStatus() { switch (_statusCode) { diff --git a/src/bidmanager.js b/src/bidmanager.js deleted file mode 100644 index c12cc4828e6..00000000000 --- a/src/bidmanager.js +++ /dev/null @@ -1,509 +0,0 @@ -import { uniques, flatten, adUnitsFilter, getBidderRequest } from './utils'; -import { getPriceBucketString } from './cpmBucketManager'; -import { nativeBidIsValid, getNativeTargeting } from './native'; -import { isValidVideoBid } from './video'; -import { getCacheUrl, store } from './videoCache'; -import { Renderer } from 'src/Renderer'; -import { config } from 'src/config'; - -var CONSTANTS = require('./constants.json'); -var AUCTION_END = CONSTANTS.EVENTS.AUCTION_END; -var utils = require('./utils.js'); -var events = require('./events'); - -var externalCallbacks = {byAdUnit: [], all: [], oneTime: null, timer: false}; -var defaultBidderSettingsMap = {}; - -/** - * Returns a list of bidders that we haven't received a response yet - * @return {array} [description] - */ -exports.getTimedOutBidders = function () { - return $$PREBID_GLOBAL$$._bidsRequested - .map(getBidderCode) - .filter(uniques) - .filter(bidder => $$PREBID_GLOBAL$$._bidsReceived - .map(getBidders) - .filter(uniques) - .indexOf(bidder) < 0); -}; - -function timestamp() { return new Date().getTime(); } - -function getBidderCode(bidSet) { - return bidSet.bidderCode; -} - -function getBidders(bid) { - return bid.bidder; -} - -function bidsBackAdUnit(adUnitCode) { - const requested = $$PREBID_GLOBAL$$._bidsRequested - .map(request => request.bids - .filter(adUnitsFilter.bind(this, $$PREBID_GLOBAL$$._adUnitCodes)) - .filter(bid => bid.placementCode === adUnitCode)) - .reduce(flatten, []) - .map(bid => { - return bid.bidder === 'indexExchange' - ? bid.sizes.length - : 1; - }).reduce(add, 0); - - const received = $$PREBID_GLOBAL$$._bidsReceived.filter(bid => bid.adUnitCode === adUnitCode).length; - return requested === received; -} - -function add(a, b) { - return a + b; -} - -function bidsBackAll() { - const requested = $$PREBID_GLOBAL$$._bidsRequested - .map(request => request.bids) - .reduce(flatten, []) - .filter(adUnitsFilter.bind(this, $$PREBID_GLOBAL$$._adUnitCodes)) - .map(bid => { - return bid.bidder === 'indexExchange' - ? bid.sizes.length - : 1; - }).reduce((a, b) => a + b, 0); - - const received = $$PREBID_GLOBAL$$._bidsReceived - .filter(adUnitsFilter.bind(this, $$PREBID_GLOBAL$$._adUnitCodes)).length; - - return requested === received; -} - -exports.bidsBackAll = function () { - return bidsBackAll(); -}; - -/* - * This function should be called to by the bidder adapter to register a bid response - */ -exports.addBidResponse = function (adUnitCode, bid) { - if (isValid()) { - prepareBidForAuction(); - - if (bid.mediaType === 'video') { - tryAddVideoBid(bid); - } else { - addBidToAuction(bid); - doCallbacksIfNeeded(); - } - } - - // Actual method logic is above. Everything below is helper functions. - - // Validate the arguments sent to us by the adapter. If this returns false, the bid should be totally ignored. - function isValid() { - function errorMessage(msg) { - return `Invalid bid from ${bid.bidderCode}. Ignoring bid: ${msg}`; - } - - if (!bid) { - utils.logError(`Some adapter tried to add an undefined bid for ${adUnitCode}.`); - return false; - } - if (!adUnitCode) { - utils.logError(errorMessage('No adUnitCode was supplied to addBidResponse.')); - return false; - } - - const bidRequest = getBidderRequest(bid.bidderCode, adUnitCode); - if (!bidRequest.start) { - utils.logError(errorMessage('Cannot find valid matching bid request.')); - return false; - } - - if (bid.mediaType === 'native' && !nativeBidIsValid(bid)) { - utils.logError(errorMessage('Native bid missing some required properties.')); - return false; - } - if (bid.mediaType === 'video' && !isValidVideoBid(bid)) { - utils.logError(errorMessage(`Video bid does not have required vastUrl or renderer property`)); - return false; - } - if (bid.mediaType === 'banner' && !validBidSize(bid)) { - utils.logError(errorMessage(`Banner bids require a width and height`)); - return false; - } - - return true; - } - - // check that the bid has a width and height set - function validBidSize(bid) { - if ((bid.width || bid.width === 0) && (bid.height || bid.height === 0)) { - return true; - } - - const adUnit = getBidderRequest(bid.bidderCode, adUnitCode); - const sizes = adUnit && adUnit.bids && adUnit.bids[0] && adUnit.bids[0].sizes; - const parsedSizes = utils.parseSizesInput(sizes); - - // if a banner impression has one valid size, we assign that size to any bid - // response that does not explicitly set width or height - if (parsedSizes.length === 1) { - const [ width, height ] = parsedSizes[0].split('x'); - bid.width = width; - bid.height = height; - return true; - } - - return false; - } - - // Postprocess the bids so that all the universal properties exist, no matter which bidder they came from. - // This should be called before addBidToAuction(). - function prepareBidForAuction() { - const bidRequest = getBidderRequest(bid.bidderCode, adUnitCode); - - Object.assign(bid, { - requestId: bidRequest.requestId, - responseTimestamp: timestamp(), - requestTimestamp: bidRequest.start, - cpm: parseFloat(bid.cpm) || 0, - bidder: bid.bidderCode, - adUnitCode - }); - - bid.timeToRespond = bid.responseTimestamp - bid.requestTimestamp; - - // Let listeners know that now is the time to adjust the bid, if they want to. - // - // CAREFUL: Publishers rely on certain bid properties to be available (like cpm), - // but others to not be set yet (like priceStrings). See #1372 and #1389. - events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bid); - - // a publisher-defined renderer can be used to render bids - const adUnitRenderer = - bidRequest.bids && bidRequest.bids[0] && bidRequest.bids[0].renderer; - - if (adUnitRenderer) { - bid.renderer = Renderer.install({ url: adUnitRenderer.url }); - bid.renderer.setRender(adUnitRenderer.render); - } - - const priceStringsObj = getPriceBucketString( - bid.cpm, - config.getConfig('customPriceBucket'), - config.getConfig('currency.granularityMultiplier') - ); - bid.pbLg = priceStringsObj.low; - bid.pbMg = priceStringsObj.med; - bid.pbHg = priceStringsObj.high; - bid.pbAg = priceStringsObj.auto; - bid.pbDg = priceStringsObj.dense; - bid.pbCg = priceStringsObj.custom; - - // if there is any key value pairs to map do here - var keyValues; - if (bid.bidderCode && (bid.cpm > 0 || bid.dealId)) { - keyValues = getKeyValueTargetingPairs(bid.bidderCode, bid); - } - - // use any targeting provided as defaults, otherwise just set from getKeyValueTargetingPairs - bid.adserverTargeting = Object.assign(bid.adserverTargeting || {}, keyValues); - } - - function doCallbacksIfNeeded() { - if (bid.timeToRespond > $$PREBID_GLOBAL$$.cbTimeout + $$PREBID_GLOBAL$$.timeoutBuffer) { - const timedOut = true; - exports.executeCallback(timedOut); - } - } - - // Add a bid to the auction. - function addBidToAuction() { - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bid); - - $$PREBID_GLOBAL$$._bidsReceived.push(bid); - - if (bid.adUnitCode && bidsBackAdUnit(bid.adUnitCode)) { - triggerAdUnitCallbacks(bid.adUnitCode); - } - - if (bidsBackAll()) { - exports.executeCallback(); - } - } - - // Video bids may fail if the cache is down, or there's trouble on the network. - function tryAddVideoBid(bid) { - if (config.getConfig('usePrebidCache')) { - store([bid], function(error, cacheIds) { - if (error) { - utils.logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); - } else { - bid.videoCacheKey = cacheIds[0].uuid; - if (!bid.vastUrl) { - bid.vastUrl = getCacheUrl(bid.videoCacheKey); - } - addBidToAuction(bid); - } - doCallbacksIfNeeded(); - }); - } else { - addBidToAuction(bid); - doCallbacksIfNeeded(); - } - } -}; - -function getKeyValueTargetingPairs(bidderCode, custBidObj) { - var keyValues = {}; - var bidder_settings = $$PREBID_GLOBAL$$.bidderSettings; - - // 1) set the keys from "standard" setting or from prebid defaults - if (custBidObj && bidder_settings) { - // initialize default if not set - const standardSettings = getStandardBidderSettings(); - setKeys(keyValues, standardSettings, custBidObj); - } - - if (bidderCode && custBidObj && bidder_settings && bidder_settings[bidderCode] && bidder_settings[bidderCode][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { - // 2) set keys from specific bidder setting override if they exist - setKeys(keyValues, bidder_settings[bidderCode], custBidObj); - custBidObj.alwaysUseBid = bidder_settings[bidderCode].alwaysUseBid; - custBidObj.sendStandardTargeting = bidder_settings[bidderCode].sendStandardTargeting; - } else if (defaultBidderSettingsMap[bidderCode]) { - // 2) set keys from standard setting. NOTE: this API doesn't seem to be in use by any Adapter - setKeys(keyValues, defaultBidderSettingsMap[bidderCode], custBidObj); - custBidObj.alwaysUseBid = defaultBidderSettingsMap[bidderCode].alwaysUseBid; - custBidObj.sendStandardTargeting = defaultBidderSettingsMap[bidderCode].sendStandardTargeting; - } - - if (custBidObj['native']) { - keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj)); - } - - return keyValues; -} - -exports.getKeyValueTargetingPairs = function() { - return getKeyValueTargetingPairs(...arguments); -}; - -function setKeys(keyValues, bidderSettings, custBidObj) { - var targeting = bidderSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]; - custBidObj.size = custBidObj.getSize(); - - utils._each(targeting, function (kvPair) { - var key = kvPair.key; - var value = kvPair.val; - - if (keyValues[key]) { - utils.logWarn('The key: ' + key + ' is getting ovewritten'); - } - - if (utils.isFn(value)) { - try { - value = value(custBidObj); - } catch (e) { - utils.logError('bidmanager', 'ERROR', e); - } - } - - if ( - ((typeof bidderSettings.suppressEmptyKeys !== 'undefined' && bidderSettings.suppressEmptyKeys === true) || - key === 'hb_deal') && // hb_deal is suppressed automatically if not set - ( - utils.isEmptyStr(value) || - value === null || - value === undefined - ) - ) { - utils.logInfo("suppressing empty key '" + key + "' from adserver targeting"); - } else { - keyValues[key] = value; - } - }); - - return keyValues; -} - -exports.registerDefaultBidderSetting = function (bidderCode, defaultSetting) { - defaultBidderSettingsMap[bidderCode] = defaultSetting; -}; - -exports.executeCallback = function (timedOut) { - // if there's still a timeout running, clear it now - if (!timedOut && externalCallbacks.timer) { - clearTimeout(externalCallbacks.timer); - } - - if (externalCallbacks.all.called !== true) { - processCallbacks(externalCallbacks.all); - externalCallbacks.all.called = true; - - if (timedOut) { - const timedOutBidders = exports.getTimedOutBidders(); - - if (timedOutBidders.length) { - events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, timedOutBidders); - } - } - } - - // execute one time callback - if (externalCallbacks.oneTime) { - events.emit(AUCTION_END); - try { - processCallbacks([externalCallbacks.oneTime]); - } catch (e) { - utils.logError('Error executing bidsBackHandler', null, e); - } finally { - externalCallbacks.oneTime = null; - externalCallbacks.timer = false; - $$PREBID_GLOBAL$$.clearAuction(); - } - } -}; - -exports.externalCallbackReset = function () { - externalCallbacks.all.called = false; -}; - -function triggerAdUnitCallbacks(adUnitCode) { - // todo : get bid responses and send in args - var singleAdUnitCode = [adUnitCode]; - processCallbacks(externalCallbacks.byAdUnit, singleAdUnitCode); -} - -function processCallbacks(callbackQueue, singleAdUnitCode) { - if (utils.isArray(callbackQueue)) { - callbackQueue.forEach(callback => { - const adUnitCodes = singleAdUnitCode || $$PREBID_GLOBAL$$._adUnitCodes; - const bids = [$$PREBID_GLOBAL$$._bidsReceived - .filter(adUnitsFilter.bind(this, adUnitCodes)) - .reduce(groupByPlacement, {})]; - - callback.apply($$PREBID_GLOBAL$$, bids); - }); - } -} - -/** - * groupByPlacement is a reduce function that converts an array of Bid objects - * to an object with placement codes as keys, with each key representing an object - * with an array of `Bid` objects for that placement - * @returns {*} as { [adUnitCode]: { bids: [Bid, Bid, Bid] } } - */ -function groupByPlacement(bidsByPlacement, bid) { - if (!bidsByPlacement[bid.adUnitCode]) { bidsByPlacement[bid.adUnitCode] = { bids: [] }; } - - bidsByPlacement[bid.adUnitCode].bids.push(bid); - - return bidsByPlacement; -} - -/** - * Add a one time callback, that is discarded after it is called - * @param {Function} callback - * @param timer Timer to clear if callback is triggered before timer time's out - */ -exports.addOneTimeCallback = function (callback, timer) { - externalCallbacks.oneTime = callback; - externalCallbacks.timer = timer; -}; - -exports.addCallback = function (id, callback, cbEvent) { - callback.id = id; - if (CONSTANTS.CB.TYPE.ALL_BIDS_BACK === cbEvent) { - externalCallbacks.all.push(callback); - } else if (CONSTANTS.CB.TYPE.AD_UNIT_BIDS_BACK === cbEvent) { - externalCallbacks.byAdUnit.push(callback); - } -}; - -// register event for bid adjustment -events.on(CONSTANTS.EVENTS.BID_ADJUSTMENT, function (bid) { - adjustBids(bid); -}); - -function adjustBids(bid) { - var code = bid.bidderCode; - var bidPriceAdjusted = bid.cpm; - let bidCpmAdjustment; - if ($$PREBID_GLOBAL$$.bidderSettings) { - if (code && $$PREBID_GLOBAL$$.bidderSettings[code] && typeof $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment === 'function') { - bidCpmAdjustment = $$PREBID_GLOBAL$$.bidderSettings[code].bidCpmAdjustment; - } else if ($$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] && typeof $$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment === 'function') { - bidCpmAdjustment = $$PREBID_GLOBAL$$.bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment; - } - if (bidCpmAdjustment) { - try { - bidPriceAdjusted = bidCpmAdjustment(bid.cpm, Object.assign({}, bid)); - } catch (e) { - utils.logError('Error during bid adjustment', 'bidmanager.js', e); - } - } - } - - if (bidPriceAdjusted >= 0) { - bid.cpm = bidPriceAdjusted; - } -} - -exports.adjustBids = function() { - return adjustBids(...arguments); -}; - -function getStandardBidderSettings() { - let granularity = config.getConfig('priceGranularity'); - let bidder_settings = $$PREBID_GLOBAL$$.bidderSettings; - if (!bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]) { - bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] = {}; - } - if (!bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { - bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = [ - { - key: 'hb_bidder', - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: 'hb_adid', - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: 'hb_pb', - val: function (bidResponse) { - if (granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { - return bidResponse.pbAg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { - return bidResponse.pbDg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { - return bidResponse.pbLg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { - return bidResponse.pbMg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { - return bidResponse.pbHg; - } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) { - return bidResponse.pbCg; - } - } - }, { - key: 'hb_size', - val: function (bidResponse) { - return bidResponse.size; - } - }, { - key: 'hb_deal', - val: function (bidResponse) { - return bidResponse.dealId; - } - } - ]; - } - return bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]; -} - -function getStandardBidderAdServerTargeting() { - return getStandardBidderSettings()[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]; -} - -exports.getStandardBidderAdServerTargeting = getStandardBidderAdServerTargeting; diff --git a/src/config.js b/src/config.js index 41ba9d25301..ff68fc7bfff 100644 --- a/src/config.js +++ b/src/config.js @@ -8,19 +8,27 @@ * continue to work during a deprecation window. */ import { isValidPriceConfig } from './cpmBucketManager'; +import find from 'core-js/library/fn/array/find'; +import includes from 'core-js/library/fn/array/includes'; +import { createHook } from 'src/hook'; const utils = require('./utils'); const DEFAULT_DEBUG = false; const DEFAULT_BIDDER_TIMEOUT = 3000; const DEFAULT_PUBLISHER_DOMAIN = window.location.origin; const DEFAULT_COOKIESYNC_DELAY = 100; -const DEFAULT_ENABLE_SEND_ALL_BIDS = false; -const DEFAULT_USERSYNC = { - syncEnabled: true, - pixelEnabled: true, - syncsPerBidder: 5, - syncDelay: 3000 -}; +const DEFAULT_ENABLE_SEND_ALL_BIDS = true; + +const DEFAULT_TIMEOUTBUFFER = 200; + +export const RANDOM = 'random'; +const FIXED = 'fixed'; + +const VALID_ORDERS = {}; +VALID_ORDERS[RANDOM] = true; +VALID_ORDERS[FIXED] = true; + +const DEFAULT_BIDDER_SEQUENCE = RANDOM; const GRANULARITY_OPTIONS = { LOW: 'low', @@ -36,118 +44,128 @@ const ALL_TOPICS = '*'; /** * @typedef {object} PrebidConfig * - * @property {bool} usePrebidCache True if we should use prebid-cache to store video bids before adding - * bids to the auction, and false otherwise. **NOTE** This must be true if you want to use the - * dfpAdServerVideo module. + * @property {string} cache.url Set a url if we should use prebid-cache to store video bids before adding + * bids to the auction. **NOTE** This must be set if you want to use the dfpAdServerVideo module. */ export function newConfig() { let listeners = []; + let defaults; + let config; - let config = { - // `debug` is equivalent to legacy `pbjs.logging` property - _debug: DEFAULT_DEBUG, - get debug() { - if ($$PREBID_GLOBAL$$.logging || $$PREBID_GLOBAL$$.logging === false) { - return $$PREBID_GLOBAL$$.logging; - } - return this._debug; - }, - set debug(val) { - this._debug = val; - }, - - // default timeout for all bids - _bidderTimeout: DEFAULT_BIDDER_TIMEOUT, - get bidderTimeout() { - return $$PREBID_GLOBAL$$.bidderTimeout || this._bidderTimeout; - }, - set bidderTimeout(val) { - this._bidderTimeout = val; - }, - - // domain where prebid is running for cross domain iframe communication - _publisherDomain: DEFAULT_PUBLISHER_DOMAIN, - get publisherDomain() { - return $$PREBID_GLOBAL$$.publisherDomain || this._publisherDomain; - }, - set publisherDomain(val) { - this._publisherDomain = val; - }, - - // delay to request cookie sync to stay out of critical path - _cookieSyncDelay: DEFAULT_COOKIESYNC_DELAY, - get cookieSyncDelay() { - return $$PREBID_GLOBAL$$.cookieSyncDelay || this._cookieSyncDelay; - }, - set cookieSyncDelay(val) { - this._cookieSyncDelay = val; - }, - - // calls existing function which may be moved after deprecation - _priceGranularity: GRANULARITY_OPTIONS.MEDIUM, - set priceGranularity(val) { - if (validatePriceGranularity(val)) { - if (typeof val === 'string') { - this._priceGranularity = (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM; - } else if (typeof val === 'object') { - this._customPriceBucket = val; - this._priceGranularity = GRANULARITY_OPTIONS.CUSTOM; - utils.logMessage('Using custom price granularity'); + function resetConfig() { + defaults = {}; + config = { + // `debug` is equivalent to legacy `pbjs.logging` property + _debug: DEFAULT_DEBUG, + get debug() { + return this._debug; + }, + set debug(val) { + this._debug = val; + }, + + // default timeout for all bids + _bidderTimeout: DEFAULT_BIDDER_TIMEOUT, + get bidderTimeout() { + return this._bidderTimeout; + }, + set bidderTimeout(val) { + this._bidderTimeout = val; + }, + + // domain where prebid is running for cross domain iframe communication + _publisherDomain: DEFAULT_PUBLISHER_DOMAIN, + get publisherDomain() { + return this._publisherDomain; + }, + set publisherDomain(val) { + this._publisherDomain = val; + }, + + // delay to request cookie sync to stay out of critical path + _cookieSyncDelay: DEFAULT_COOKIESYNC_DELAY, + get cookieSyncDelay() { + return $$PREBID_GLOBAL$$.cookieSyncDelay || this._cookieSyncDelay; + }, + set cookieSyncDelay(val) { + this._cookieSyncDelay = val; + }, + + // calls existing function which may be moved after deprecation + _priceGranularity: GRANULARITY_OPTIONS.MEDIUM, + set priceGranularity(val) { + if (validatePriceGranularity(val)) { + if (typeof val === 'string') { + this._priceGranularity = (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM; + } else if (typeof val === 'object') { + this._customPriceBucket = val; + this._priceGranularity = GRANULARITY_OPTIONS.CUSTOM; + utils.logMessage('Using custom price granularity'); + } } - } - }, - get priceGranularity() { - return this._priceGranularity; - }, - - _customPriceBucket: {}, - get customPriceBucket() { - return this._customPriceBucket; - }, - - _sendAllBids: DEFAULT_ENABLE_SEND_ALL_BIDS, - get enableSendAllBids() { - return this._sendAllBids; - }, - set enableSendAllBids(val) { - this._sendAllBids = val; - }, - - // calls existing function which may be moved after deprecation - set bidderSequence(val) { - $$PREBID_GLOBAL$$.setBidderSequence(val); - }, - - // calls existing function which may be moved after deprecation - set s2sConfig(val) { - $$PREBID_GLOBAL$$.setS2SConfig(val); - }, - - // userSync defaults - userSync: DEFAULT_USERSYNC - }; + }, + get priceGranularity() { + return this._priceGranularity; + }, - function hasGranularity(val) { - return Object.keys(GRANULARITY_OPTIONS).find(option => val === GRANULARITY_OPTIONS[option]); - } + _customPriceBucket: {}, + get customPriceBucket() { + return this._customPriceBucket; + }, + + _sendAllBids: DEFAULT_ENABLE_SEND_ALL_BIDS, + get enableSendAllBids() { + return this._sendAllBids; + }, + set enableSendAllBids(val) { + this._sendAllBids = val; + }, - function validatePriceGranularity(val) { - if (!val) { - utils.logError('Prebid Error: no value passed to `setPriceGranularity()`'); - return false; + _bidderSequence: DEFAULT_BIDDER_SEQUENCE, + get bidderSequence() { + return this._bidderSequence; + }, + set bidderSequence(val) { + if (VALID_ORDERS[val]) { + this._bidderSequence = val; + } else { + utils.logWarn(`Invalid order: ${val}. Bidder Sequence was not set.`); + } + }, + + // timeout buffer to adjust for bidder CDN latency + _timoutBuffer: DEFAULT_TIMEOUTBUFFER, + get timeoutBuffer() { + return this._timoutBuffer; + }, + set timeoutBuffer(val) { + this._timoutBuffer = val; + }, + + }; + + function hasGranularity(val) { + return find(Object.keys(GRANULARITY_OPTIONS), option => val === GRANULARITY_OPTIONS[option]); } - if (typeof val === 'string') { - if (!hasGranularity(val)) { - utils.logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.'); - } - } else if (typeof val === 'object') { - if (!isValidPriceConfig(val)) { - utils.logError('Invalid custom price value passed to `setPriceGranularity()`'); + + function validatePriceGranularity(val) { + if (!val) { + utils.logError('Prebid Error: no value passed to `setPriceGranularity()`'); return false; } + if (typeof val === 'string') { + if (!hasGranularity(val)) { + utils.logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.'); + } + } else if (typeof val === 'object') { + if (!isValidPriceConfig(val)) { + utils.logError('Invalid custom price value passed to `setPriceGranularity()`'); + return false; + } + } + return true; } - return true; } /* @@ -171,14 +189,41 @@ export function newConfig() { * Sets configuration given an object containing key-value pairs and calls * listeners that were added by the `subscribe` function */ - function setConfig(options) { + let setConfig = createHook('asyncSeries', function setConfig(options) { if (typeof options !== 'object') { utils.logError('setConfig options must be an object'); return; } + let topics = Object.keys(options); + let topicalConfig = {}; + + topics.forEach(topic => { + let option = options[topic]; + + if (typeof defaults[topic] === 'object' && typeof option === 'object') { + option = Object.assign({}, defaults[topic], option); + } + + topicalConfig[topic] = config[topic] = option; + }); + + callSubscribers(topicalConfig); + }); + + /** + * Sets configuration defaults which setConfig values can be applied on top of + * @param {object} options + */ + function setDefaults(options) { + if (typeof defaults !== 'object') { + utils.logError('defaults must be an object'); + return; + } + + Object.assign(defaults, options); + // Add default values to config as well Object.assign(config, options); - callSubscribers(options); } /* @@ -233,7 +278,7 @@ export function newConfig() { // call subscribers of a specific topic, passing only that configuration listeners - .filter(listener => TOPICS.includes(listener.topic)) + .filter(listener => includes(TOPICS, listener.topic)) .forEach(listener => { listener.callback({ [listener.topic]: options[listener.topic] }); }); @@ -244,9 +289,13 @@ export function newConfig() { .forEach(listener => listener.callback(options)); } + resetConfig(); + return { getConfig, - setConfig + setConfig, + setDefaults, + resetConfig }; } diff --git a/src/constants.json b/src/constants.json index 806b3790c12..c8a7c3ebefc 100644 --- a/src/constants.json +++ b/src/constants.json @@ -31,9 +31,18 @@ "BID_REQUESTED": "bidRequested", "BID_RESPONSE": "bidResponse", "BID_WON": "bidWon", + "BIDDER_DONE": "bidderDone", "SET_TARGETING": "setTargeting", "REQUEST_BIDS": "requestBids", - "ADD_AD_UNITS": "addAdUnits" + "ADD_AD_UNITS": "addAdUnits", + "AD_RENDER_FAILED" : "adRenderFailed" + }, + "AD_RENDER_FAILED_REASON" : { + "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocuemnt", + "NO_AD": "noAd", + "EXCEPTION": "exception", + "CANNOT_FIND_AD": "cannotFindAd", + "MISSING_DOC_OR_ADID": "missingDocOrAdid" }, "EVENT_ID_PATHS": { "bidWon": "adUnitCode" @@ -51,13 +60,12 @@ "hb_adid", "hb_pb", "hb_size", - "hb_deal" + "hb_deal", + "hb_source", + "hb_format" ], "S2S" : { - "DEFAULT_ENDPOINT" : "https://prebid.adnxs.com/pbs/v1/auction", "SRC" : "s2s", - "ADAPTER" : "prebidServer", - "SYNC_ENDPOINT" : "https://prebid.adnxs.com/pbs/v1/cookie_sync", "SYNCED_BIDDERS_KEY": "pbjsSyncs" } } diff --git a/src/cpmBucketManager.js b/src/cpmBucketManager.js index 5eb66bc1376..c2250838bcb 100644 --- a/src/cpmBucketManager.js +++ b/src/cpmBucketManager.js @@ -1,3 +1,4 @@ +import find from 'core-js/library/fn/array/find'; const utils = require('src/utils'); const _defaultPrecision = 2; @@ -86,7 +87,7 @@ function getCpmStringValue(cpm, config, granularityMultiplier) { }, { 'max': 0, }); - let bucket = config.buckets.find(bucket => { + let bucket = find(config.buckets, bucket => { if (cpm > cap.max * granularityMultiplier) { // cpm exceeds cap, just return the cap. let precision = bucket.precision; @@ -99,7 +100,7 @@ function getCpmStringValue(cpm, config, granularityMultiplier) { } }); if (bucket) { - cpmStr = getCpmTarget(cpm, bucket.increment, bucket.precision, granularityMultiplier); + cpmStr = getCpmTarget(cpm, bucket, granularityMultiplier); } return cpmStr; } @@ -117,12 +118,17 @@ function isValidPriceConfig(config) { return isValid; } -function getCpmTarget(cpm, increment, precision, granularityMultiplier) { - if (typeof precision === 'undefined') { - precision = _defaultPrecision; - } - let bucketSize = 1 / (increment * granularityMultiplier); - return (Math.floor(cpm * bucketSize) / bucketSize).toFixed(precision); +function getCpmTarget(cpm, bucket, granularityMultiplier) { + const precision = typeof bucket.precision !== 'undefined' ? bucket.precision : _defaultPrecision; + const increment = bucket.increment * granularityMultiplier; + const bucketMin = bucket.min * granularityMultiplier; + + // start increments at the bucket min and then add bucket min back to arrive at the correct rounding + let cpmTarget = ((Math.floor((cpm - bucketMin) / increment)) * increment) + bucketMin; + // force to 10 decimal places to deal with imprecise decimal/binary conversions + // (for example 0.1 * 3 = 0.30000000000000004) + cpmTarget = Number(cpmTarget.toFixed(10)); + return cpmTarget.toFixed(precision); } export { getPriceBucketString, isValidPriceConfig }; diff --git a/src/hook.js b/src/hook.js new file mode 100644 index 00000000000..6c6cefdc56c --- /dev/null +++ b/src/hook.js @@ -0,0 +1,78 @@ + +/** + * @typedef {function} HookedFunction + * @property {function(function(), [number])} addHook A method that takes a new function to attach as a hook + * to the HookedFunction + * @property {function(function())} removeHook A method to remove attached hooks + */ + +/** + * A map of global hook methods to allow easy extension of hooked functions that are intended to be extended globally + * @type {{}} + */ +export const hooks = {}; + +/** + * A utility function for allowing a regular function to be extensible with additional hook functions + * @param {string} type The method for applying all attached hooks when this hooked function is called + * @param {function()} fn The function to make hookable + * @param {string} hookName If provided this allows you to register a name for a global hook to have easy access to + * the addHook and removeHook methods for that hook (which are usually accessed as methods on the function itself) + * @returns {HookedFunction} A new function that implements the HookedFunction interface + */ +export function createHook(type, fn, hookName) { + let _hooks = [{fn, priority: 0}]; + + let types = { + sync: function(...args) { + _hooks.forEach(hook => { + hook.fn.apply(this, args); + }); + }, + asyncSeries: function(...args) { + let curr = 0; + + const asyncSeriesNext = (...args) => { + let hook = _hooks[++curr]; + if (typeof hook === 'object' && typeof hook.fn === 'function') { + return hook.fn.apply(this, args.concat(asyncSeriesNext)) + } + }; + + return _hooks[curr].fn.apply(this, args.concat(asyncSeriesNext)); + } + }; + + if (!types[type]) { + throw 'invalid hook type'; + } + + let methods = { + addHook: function(fn, priority = 10) { + if (typeof fn === 'function') { + _hooks.push({ + fn, + priority: priority + }); + + _hooks.sort((a, b) => b.priority - a.priority); + } + }, + removeHook: function(removeFn) { + _hooks = _hooks.filter(hook => hook.fn === fn || hook.fn !== removeFn); + } + }; + + if (typeof hookName === 'string') { + hooks[hookName] = methods; + } + + function hookedFn(...args) { + if (_hooks.length === 1 && _hooks[0].fn === fn) { + return fn.apply(this, args); + } + return types[type].apply(this, args); + } + + return Object.assign(hookedFn, methods); +} diff --git a/src/native.js b/src/native.js index c992cf9ad61..3d2ec2fe688 100644 --- a/src/native.js +++ b/src/native.js @@ -1,4 +1,5 @@ -import { deepAccess, getBidRequest, logError, triggerPixel } from './utils'; +import { deepAccess, getBidRequest, logError, triggerPixel, insertHtmlIntoIframe } from './utils'; +import includes from 'core-js/library/fn/array/includes'; export const nativeAdapters = []; @@ -46,7 +47,7 @@ export function processNativeAdUnitParams(params) { * Check if the native type specified in the adUnit is supported by Prebid. */ function typeIsSupported(type) { - if (!(type && Object.keys(SUPPORTED_TYPES).includes(type))) { + if (!(type && includes(Object.keys(SUPPORTED_TYPES), type))) { logError(`${type} nativeParam is not supported`); return false; } @@ -64,25 +65,38 @@ export const nativeAdUnit = adUnit => { const mediaTypes = deepAccess(adUnit, 'mediaTypes.native'); return mediaType || mediaTypes; } -export const nativeBidder = bid => nativeAdapters.includes(bid.bidder); +export const nativeBidder = bid => includes(nativeAdapters, bid.bidder); export const hasNonNativeBidder = adUnit => adUnit.bids.filter(bid => !nativeBidder(bid)).length; -/* +/** * Validate that the native assets on this bid contain all assets that were * marked as required in the adUnit configuration. + * @param {Bid} bid Native bid to validate + * @param {BidRequest[]} bidRequests All bid requests for an auction + * @return {Boolean} If object is valid */ -export function nativeBidIsValid(bid) { - const bidRequest = getBidRequest(bid.adId); - if (!bidRequest) { - return false; - } +export function nativeBidIsValid(bid, bidRequests) { + const bidRequest = getBidRequest(bid.adId, bidRequests); + if (!bidRequest) { return false; } // all native bid responses must define a landing page url if (!deepAccess(bid, 'native.clickUrl')) { return false; } + if (deepAccess(bid, 'native.image')) { + if (!deepAccess(bid, 'native.image.height') || !deepAccess(bid, 'native.image.width')) { + return false; + } + } + + if (deepAccess(bid, 'native.icon')) { + if (!deepAccess(bid, 'native.icon.height') || !deepAccess(bid, 'native.icon.width')) { + return false; + } + } + const requestedAssets = bidRequest.nativeParams; if (!requestedAssets) { return true; @@ -95,7 +109,7 @@ export function nativeBidIsValid(bid) { key => bid['native'][key] ); - return requiredAssets.every(asset => returnedAssets.includes(asset)); + return requiredAssets.every(asset => includes(returnedAssets, asset)); } /* @@ -131,6 +145,10 @@ export function fireNativeTrackers(message, adObject) { trackers = adObject['native'] && adObject['native'].clickTrackers; } else { trackers = adObject['native'] && adObject['native'].impressionTrackers; + + if (adObject['native'] && adObject['native'].javascriptTrackers) { + insertHtmlIntoIframe(adObject['native'].javascriptTrackers); + } } (trackers || []).forEach(triggerPixel); @@ -146,7 +164,13 @@ export function getNativeTargeting(bid) { Object.keys(bid['native']).forEach(asset => { const key = NATIVE_KEYS[asset]; - const value = bid['native'][asset]; + let value = bid['native'][asset]; + + // native image-type assets can be a string or an object with a url prop + if (typeof value === 'object' && value.url) { + value = value.url; + } + if (key) { keyValues[key] = value; } diff --git a/src/polyfill.js b/src/polyfill.js deleted file mode 100644 index 1c8913824ae..00000000000 --- a/src/polyfill.js +++ /dev/null @@ -1,14 +0,0 @@ -/** @module polyfill -Misc polyfills -*/ -require('core-js/fn/array/find'); -require('core-js/fn/array/find-index'); -require('core-js/fn/array/includes'); -require('core-js/fn/object/assign'); - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger -Number.isInteger = Number.isInteger || function(value) { - return typeof value === 'number' && - isFinite(value) && - Math.floor(value) === value; -}; diff --git a/src/prebid.js b/src/prebid.js index 5e3168a829d..0dcf286c3bf 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,68 +1,38 @@ -/** @module $$PREBID_GLOBAL$$ */ +/** @module pbjs */ import { getGlobal } from './prebidGlobal'; -import { flatten, uniques, isGptPubadsDefined, adUnitsFilter } from './utils'; -import { videoAdUnit, hasNonVideoBidder } from './video'; -import { nativeAdUnit, nativeBidder, hasNonNativeBidder } from './native'; -import './polyfill'; -import { parse as parseURL, format as formatURL } from './url'; +import { flatten, uniques, isGptPubadsDefined, adUnitsFilter, removeRequestId } from './utils'; import { listenMessagesFromCreative } from './secureCreatives'; import { userSync } from 'src/userSync.js'; import { loadScript } from './adloader'; -import { setAjaxTimeout } from './ajax'; import { config } from './config'; - -var $$PREBID_GLOBAL$$ = getGlobal(); - -var CONSTANTS = require('./constants.json'); -var utils = require('./utils.js'); -var bidmanager = require('./bidmanager.js'); -var adaptermanager = require('./adaptermanager'); -var bidfactory = require('./bidfactory'); -var events = require('./events'); -var adserver = require('./adserver.js'); -var targeting = require('./targeting.js'); -const { syncUsers, triggerUserSyncs } = userSync; +import { auctionManager } from './auctionManager'; +import { targeting, getOldestBid, RENDERED, BID_TARGETING_SET } from './targeting'; +import { createHook } from 'src/hook'; +import includes from 'core-js/library/fn/array/includes'; + +const $$PREBID_GLOBAL$$ = getGlobal(); +const CONSTANTS = require('./constants.json'); +const utils = require('./utils.js'); +const adaptermanager = require('./adaptermanager'); +const bidfactory = require('./bidfactory'); +const events = require('./events'); +const { triggerUserSyncs } = userSync; /* private variables */ +const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, AD_RENDER_FAILED } = CONSTANTS.EVENTS; +const { PREVENT_WRITING_ON_MAIN_DOCUMENT, NO_AD, EXCEPTION, CANNOT_FIND_AD, MISSING_DOC_OR_ADID } = CONSTANTS.AD_RENDER_FAILED_REASON; -var BID_WON = CONSTANTS.EVENTS.BID_WON; -var SET_TARGETING = CONSTANTS.EVENTS.SET_TARGETING; -var ADD_AD_UNITS = CONSTANTS.EVENTS.ADD_AD_UNITS; - -var auctionRunning = false; -var bidRequestQueue = []; - -var eventValidators = { +const eventValidators = { bidWon: checkDefinedPlacement }; /* Public vars */ - -$$PREBID_GLOBAL$$._bidsRequested = []; -$$PREBID_GLOBAL$$._bidsReceived = []; -// _adUnitCodes stores the current filter to use for adUnits as an array of adUnitCodes -$$PREBID_GLOBAL$$._adUnitCodes = []; -$$PREBID_GLOBAL$$._winningBids = []; -$$PREBID_GLOBAL$$._adsReceived = []; - $$PREBID_GLOBAL$$.bidderSettings = $$PREBID_GLOBAL$$.bidderSettings || {}; -/** @deprecated - use pbjs.setConfig({ bidderTimeout: }) */ -$$PREBID_GLOBAL$$.bidderTimeout = $$PREBID_GLOBAL$$.bidderTimeout; - // current timeout set in `requestBids` or to default `bidderTimeout` $$PREBID_GLOBAL$$.cbTimeout = $$PREBID_GLOBAL$$.cbTimeout || 200; -// timeout buffer to adjust for bidder CDN latency -$$PREBID_GLOBAL$$.timeoutBuffer = 200; - -/** @deprecated - use pbjs.setConfig({ debug: }) */ -$$PREBID_GLOBAL$$.logging = $$PREBID_GLOBAL$$.logging; - -/** @deprecated - use pbjs.setConfig({ publisherDomain: ) */ -$$PREBID_GLOBAL$$.publisherDomain = $$PREBID_GLOBAL$$.publisherDomain; - // let the world know we are loaded $$PREBID_GLOBAL$$.libLoaded = true; @@ -77,11 +47,11 @@ $$PREBID_GLOBAL$$.adUnits = $$PREBID_GLOBAL$$.adUnits || []; $$PREBID_GLOBAL$$.triggerUserSyncs = triggerUserSyncs; function checkDefinedPlacement(id) { - var placementCodes = $$PREBID_GLOBAL$$._bidsRequested.map(bidSet => bidSet.bids.map(bid => bid.placementCode)) + var adUnitCodes = auctionManager.getBidsRequested().map(bidSet => bidSet.bids.map(bid => bid.adUnitCode)) .reduce(flatten) .filter(uniques); - if (!utils.contains(placementCodes, id)) { + if (!utils.contains(adUnitCodes, id)) { utils.logError('The "' + id + '" placement is not defined.'); return; } @@ -89,18 +59,6 @@ function checkDefinedPlacement(id) { return true; } -/** - * When a request for bids is made any stale bids remaining will be cleared for - * a placement included in the outgoing bid request. - */ -function clearPlacements() { - $$PREBID_GLOBAL$$._bidsRequested = []; - - // leave bids received for ad slots not in this bid request - $$PREBID_GLOBAL$$._bidsReceived = $$PREBID_GLOBAL$$._bidsReceived - .filter(bid => !$$PREBID_GLOBAL$$._adUnitCodes.includes(bid.adUnitCode)); -} - function setRenderSize(doc, width, height) { if (doc.defaultView && doc.defaultView.frameElement) { doc.defaultView.frameElement.width = width; @@ -117,7 +75,7 @@ function setRenderSize(doc, width, height) { /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param {string} [adunitCode] adUnitCode to get the bid responses for - * @alias module:$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr + * @alias module:pbjs.getAdserverTargetingForAdUnitCodeStr * @return {Array} returnObj return bids array */ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { @@ -135,6 +93,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param adUnitCode {string} adUnitCode to get the bid responses for + * @alias module:pbjs.getAdserverTargetingForAdUnitCode * @returns {Object} returnObj return bids */ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { @@ -144,50 +103,37 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { /** * returns all ad server targeting for all ad units * @return {Object} Map of adUnitCodes and targeting values [] - * @alias module:$$PREBID_GLOBAL$$.getAdserverTargeting + * @alias module:pbjs.getAdserverTargeting */ $$PREBID_GLOBAL$$.getAdserverTargeting = function (adUnitCode) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargeting', arguments); - return targeting.getAllTargeting(adUnitCode) - .map(targeting => { - return { - [Object.keys(targeting)[0]]: targeting[Object.keys(targeting)[0]] - .map(target => { - return { - [Object.keys(target)[0]]: target[Object.keys(target)[0]].join(', ') - }; - }).reduce((p, c) => Object.assign(c, p), {}) - }; - }) - .reduce(function (accumulator, targeting) { - var key = Object.keys(targeting)[0]; - accumulator[key] = Object.assign({}, accumulator[key], targeting[key]); - return accumulator; - }, {}); + let bidsReceived = auctionManager.getBidsReceived(); + return targeting.getAllTargeting(adUnitCode, bidsReceived); }; /** * This function returns the bid responses at the given moment. - * @alias module:$$PREBID_GLOBAL$$.getBidResponses + * @alias module:pbjs.getBidResponses * @return {Object} map | object that contains the bidResponses */ $$PREBID_GLOBAL$$.getBidResponses = function () { utils.logInfo('Invoking $$PREBID_GLOBAL$$.getBidResponses', arguments); - const responses = $$PREBID_GLOBAL$$._bidsReceived - .filter(adUnitsFilter.bind(this, $$PREBID_GLOBAL$$._adUnitCodes)); + const responses = auctionManager.getBidsReceived() + .filter(adUnitsFilter.bind(this, auctionManager.getAdUnitCodes())); - // find the last requested id to get responses for most recent auction only - const currentRequestId = responses && responses.length && responses[responses.length - 1].requestId; + // find the last auction id to get responses for most recent auction only + const currentAuctionId = responses && responses.length && responses[responses.length - 1].auctionId; - return responses.map(bid => bid.adUnitCode) + return responses + .map(bid => bid.adUnitCode) .filter(uniques).map(adUnitCode => responses - .filter(bid => bid.requestId === currentRequestId && bid.adUnitCode === adUnitCode)) + .filter(bid => bid.auctionId === currentAuctionId && bid.adUnitCode === adUnitCode)) .filter(bids => bids && bids[0] && bids[0].adUnitCode) .map(bids => { return { - [bids[0].adUnitCode]: { bids: bids } + [bids[0].adUnitCode]: { bids: bids.map(removeRequestId) } }; }) .reduce((a, b) => Object.assign(a, b), {}); @@ -196,21 +142,21 @@ $$PREBID_GLOBAL$$.getBidResponses = function () { /** * Returns bidResponses for the specified adUnitCode * @param {string} adUnitCode adUnitCode - * @alias module:$$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode + * @alias module:pbjs.getBidResponsesForAdUnitCode * @return {Object} bidResponse object */ $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode = function (adUnitCode) { - const bids = $$PREBID_GLOBAL$$._bidsReceived.filter(bid => bid.adUnitCode === adUnitCode); + const bids = auctionManager.getBidsReceived().filter(bid => bid.adUnitCode === adUnitCode); return { - bids: bids + bids: bids.map(removeRequestId) }; }; /** * Set query string targeting on one or more GPT ad units. * @param {(string|string[])} adUnit a single `adUnit.code` or multiple. - * @alias module:$$PREBID_GLOBAL$$.setTargetingForGPTAsync + * @alias module:pbjs.setTargetingForGPTAsync */ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForGPTAsync', arguments); @@ -226,12 +172,16 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit) { targeting.resetPresetTargeting(adUnit); // now set new targeting keys - targeting.setTargeting(targetingSet); + targeting.setTargetingForGPT(targetingSet); // emit event - events.emit(SET_TARGETING); + events.emit(SET_TARGETING, targetingSet); }; +/** + * Set query string targeting on all AST (AppNexus Seller Tag) ad units. Note that this function has to be called after all ad units on page are defined. For working example code, see [Using Prebid.js with AppNexus Publisher Ad Server](http://prebid.org/dev-docs/examples/use-prebid-with-appnexus-ad-server.html). + * @alias module:pbjs.setTargetingForAst + */ $$PREBID_GLOBAL$$.setTargetingForAst = function() { utils.logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForAn', arguments); if (!targeting.isApntagDefined()) { @@ -242,86 +192,95 @@ $$PREBID_GLOBAL$$.setTargetingForAst = function() { targeting.setTargetingForAst(); // emit event - events.emit(SET_TARGETING); + events.emit(SET_TARGETING, targeting.getAllTargeting()); }; -/** - * Returns a bool if all the bids have returned or timed out - * @alias module:$$PREBID_GLOBAL$$.allBidsAvailable - * @return {bool} all bids available - * - * @deprecated This function will be removed in Prebid 1.0 - * Alternative solution is in progress. - * See https://github.com/prebid/Prebid.js/issues/1087 for more details. - */ -$$PREBID_GLOBAL$$.allBidsAvailable = function () { - utils.logWarn('$$PREBID_GLOBAL$$.allBidsAvailable will be removed in Prebid 1.0. Alternative solution is in progress. See https://github.com/prebid/Prebid.js/issues/1087 for more details.'); - utils.logInfo('Invoking $$PREBID_GLOBAL$$.allBidsAvailable', arguments); - return bidmanager.bidsBackAll(); -}; +function emitAdRenderFail(reason, message, bid) { + const data = {}; + data.reason = reason; + data.message = message; + if (bid) { + data.bid = bid; + } + + utils.logError(message); + events.emit(AD_RENDER_FAILED, data); +} /** * This function will render the ad (based on params) in the given iframe document passed through. * Note that doc SHOULD NOT be the parent document page as we can't doc.write() asynchronously * @param {HTMLDocument} doc document * @param {string} id bid id to locate the ad - * @alias module:$$PREBID_GLOBAL$$.renderAd + * @alias module:pbjs.renderAd */ $$PREBID_GLOBAL$$.renderAd = function (doc, id) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.renderAd', arguments); utils.logMessage('Calling renderAd with adId :' + id); + if (doc && id) { try { // lookup ad by ad Id - const bid = $$PREBID_GLOBAL$$._bidsReceived.find(bid => bid.adId === id); + const bid = auctionManager.findBidByAdId(id); if (bid) { + bid.status = RENDERED; // replace macros according to openRTB with price paid = bid.cpm bid.ad = utils.replaceAuctionPrice(bid.ad, bid.cpm); - bid.url = utils.replaceAuctionPrice(bid.url, bid.cpm); + bid.adUrl = utils.replaceAuctionPrice(bid.adUrl, bid.cpm); // save winning bids - $$PREBID_GLOBAL$$._winningBids.push(bid); + auctionManager.addWinningBid(bid); // emit 'bid won' event here events.emit(BID_WON, bid); - const { height, width, ad, mediaType, adUrl: url, renderer } = bid; + const { height, width, ad, mediaType, adUrl, renderer } = bid; + + const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`); + utils.insertElement(creativeComment, doc, 'body'); if (renderer && renderer.url) { renderer.render(bid); } else if ((doc === document && !utils.inIframe()) || mediaType === 'video') { - utils.logError(`Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`); + const message = `Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`; + emitAdRenderFail(PREVENT_WRITING_ON_MAIN_DOCUMENT, message, bid); } else if (ad) { doc.write(ad); doc.close(); setRenderSize(doc, width, height); - } else if (url) { + utils.callBurl(bid); + } else if (adUrl) { const iframe = utils.createInvisibleIframe(); iframe.height = height; iframe.width = width; iframe.style.display = 'inline'; iframe.style.overflow = 'hidden'; - iframe.src = url; + iframe.src = adUrl; utils.insertElement(iframe, doc, 'body'); setRenderSize(doc, width, height); + utils.callBurl(bid); } else { - utils.logError('Error trying to write ad. No ad for bid response id: ' + id); + const message = `Error trying to write ad. No ad for bid response id: ${id}`; + emitAdRenderFail(NO_AD, message, bid); } } else { - utils.logError('Error trying to write ad. Cannot find ad by given id : ' + id); + const message = `Error trying to write ad. Cannot find ad by given id : ${id}`; + emitAdRenderFail(CANNOT_FIND_AD, message); } } catch (e) { - utils.logError('Error trying to write ad Id :' + id + ' to the page:' + e.message); + const message = `Error trying to write ad Id :${id} to the page:${e.message}`; + emitAdRenderFail(EXCEPTION, message); } } else { - utils.logError('Error trying to write ad Id :' + id + ' to the page. Missing document or adId'); + const message = `Error trying to write ad Id :${id} to the page. Missing document or adId`; + emitAdRenderFail(MISSING_DOC_OR_ADID, message); } }; /** * Remove adUnit from the $$PREBID_GLOBAL$$ configuration * @param {string} adUnitCode the adUnitCode to remove - * @alias module:$$PREBID_GLOBAL$$.removeAdUnit + * @alias module:pbjs.removeAdUnit */ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.removeAdUnit', arguments); @@ -334,107 +293,83 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { } }; -$$PREBID_GLOBAL$$.clearAuction = function() { - auctionRunning = false; - // Only automatically sync if the publisher has not chosen to "enableOverride" - let userSyncConfig = config.getConfig('userSync') || {}; - if (!userSyncConfig.enableOverride) { - // Delay the auto sync by the config delay - syncUsers(userSyncConfig.syncDelay); - } - - utils.logMessage('Prebid auction cleared'); - if (bidRequestQueue.length) { - bidRequestQueue.shift()(); - } -}; - /** * @param {Object} requestOptions * @param {function} requestOptions.bidsBackHandler * @param {number} requestOptions.timeout * @param {Array} requestOptions.adUnits * @param {Array} requestOptions.adUnitCodes + * @param {Array} requestOptions.labels + * @alias module:pbjs.requestBids */ -$$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes } = {}) { - events.emit('requestBids'); - const cbTimeout = $$PREBID_GLOBAL$$.cbTimeout = timeout || config.getConfig('bidderTimeout'); +$$PREBID_GLOBAL$$.requestBids = createHook('asyncSeries', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels } = {}) { + events.emit(REQUEST_BIDS); + const cbTimeout = timeout || config.getConfig('bidderTimeout'); adUnits = adUnits || $$PREBID_GLOBAL$$.adUnits; utils.logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); if (adUnitCodes && adUnitCodes.length) { // if specific adUnitCodes supplied filter adUnits for those codes - adUnits = adUnits.filter(unit => adUnitCodes.includes(unit.code)); + adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); } else { // otherwise derive adUnitCodes from adUnits adUnitCodes = adUnits && adUnits.map(unit => unit.code); } - // for video-enabled adUnits, only request bids if all bidders support video - const invalidVideoAdUnits = adUnits.filter(videoAdUnit).filter(hasNonVideoBidder); - invalidVideoAdUnits.forEach(adUnit => { - utils.logError(`adUnit ${adUnit.code} has 'mediaType' set to 'video' but contains a bidder that doesn't support video. No Prebid demand requests will be triggered for this adUnit.`); - for (let i = 0; i < adUnits.length; i++) { - if (adUnits[i].code === adUnit.code) { adUnits.splice(i, 1); } - } - }); - - // for native-enabled adUnits, only request bids for bidders that support native - adUnits.filter(nativeAdUnit).filter(hasNonNativeBidder).forEach(adUnit => { - const nonNativeBidders = adUnit.bids - .filter(bid => !nativeBidder(bid)) - .map(bid => bid.bidder) - .join(', '); - - utils.logError(`adUnit ${adUnit.code} has 'mediaType' set to 'native' but contains non-native bidder(s) ${nonNativeBidders}. No Prebid demand requests will be triggered for those bidders.`); - adUnit.bids = adUnit.bids.filter(nativeBidder); - }); - - if (auctionRunning) { - bidRequestQueue.push(() => { - $$PREBID_GLOBAL$$.requestBids({ bidsBackHandler, timeout: cbTimeout, adUnits, adUnitCodes }); + /* + * for a given adunit which supports a set of mediaTypes + * and a given bidder which supports a set of mediaTypes + * a bidder is eligible to participate on the adunit + * if it supports at least one of the mediaTypes on the adunit + */ + adUnits.forEach(adUnit => { + // get the adunit's mediaTypes, defaulting to banner if mediaTypes isn't present + const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || {'banner': 'banner'}); + + // get the bidder's mediaTypes + const bidders = adUnit.bids.map(bid => bid.bidder); + const bidderRegistry = adaptermanager.bidderRegistry; + + bidders.forEach(bidder => { + const adapter = bidderRegistry[bidder]; + const spec = adapter && adapter.getSpec && adapter.getSpec() + // banner is default if not specified in spec + const bidderMediaTypes = (spec && spec.supportedMediaTypes) || ['banner']; + + // check if the bidder's mediaTypes are not in the adUnit's mediaTypes + const bidderEligible = adUnitMediaTypes.some(type => includes(bidderMediaTypes, type)); + if (!bidderEligible) { + // drop the bidder from the ad unit if it's not compatible + utils.logWarn(utils.unsupportedBidderMessage(adUnit, bidder)); + adUnit.bids = adUnit.bids.filter(bid => bid.bidder !== bidder); + } }); - return; - } - - auctionRunning = true; - - // we will use adUnitCodes for filtering the current auction - $$PREBID_GLOBAL$$._adUnitCodes = adUnitCodes; - - bidmanager.externalCallbackReset(); - clearPlacements(); + }); if (!adUnits || adUnits.length === 0) { utils.logMessage('No adUnits configured. No bids requested.'); if (typeof bidsBackHandler === 'function') { - bidmanager.addOneTimeCallback(bidsBackHandler, false); + // executeCallback, this will only be called in case of first request + try { + bidsBackHandler(); + } catch (e) { + utils.logError('Error executing bidsBackHandler', null, e); + } } - bidmanager.executeCallback(); return; } - // set timeout for all bids - const timedOut = true; - const timeoutCallback = bidmanager.executeCallback.bind(bidmanager, timedOut); - const timer = setTimeout(timeoutCallback, cbTimeout); - setAjaxTimeout(cbTimeout); - if (typeof bidsBackHandler === 'function') { - bidmanager.addOneTimeCallback(bidsBackHandler, timer); - } - - adaptermanager.callBids({ adUnits, adUnitCodes, cbTimeout }); - if ($$PREBID_GLOBAL$$._bidsRequested.length === 0) { - bidmanager.executeCallback(); - } -}; + const auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels}); + auction.callBids(); + return auction; +}); /** * * Add adunit(s) * @param {Array|Object} adUnitArr Array of adUnits or single adUnit Object. - * @alias module:$$PREBID_GLOBAL$$.addAdUnits + * @alias module:pbjs.addAdUnits */ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); @@ -456,6 +391,7 @@ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { * @param {string} event the name of the event * @param {Function} handler a callback to set on event * @param {string} id an identifier in the context of the event + * @alias module:pbjs.onEvent * * This API call allows you to register a callback to handle a Prebid.js event. * An optional `id` parameter provides more finely-grained event callback registration. @@ -486,6 +422,7 @@ $$PREBID_GLOBAL$$.onEvent = function (event, handler, id) { * @param {string} event the name of the event * @param {Function} handler a callback to remove from the event * @param {string} id an identifier in the context of the event (see `$$PREBID_GLOBAL$$.onEvent`) + * @alias module:pbjs.offEvent */ $$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.offEvent', arguments); @@ -496,49 +433,11 @@ $$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { events.off(event, handler, id); }; -/** - * Add a callback event - * @param {string} eventStr event to attach callback to Options: "allRequestedBidsBack" | "adUnitBidsBack" - * @param {Function} func function to execute. Parameters passed into the function: (bidResObj), [adUnitCode]); - * @alias module:$$PREBID_GLOBAL$$.addCallback - * @returns {string} id for callback - * - * @deprecated This function will be removed in Prebid 1.0 - * Please use onEvent instead. - */ -$$PREBID_GLOBAL$$.addCallback = function (eventStr, func) { - utils.logWarn('$$PREBID_GLOBAL$$.addCallback will be removed in Prebid 1.0. Please use onEvent instead'); - utils.logInfo('Invoking $$PREBID_GLOBAL$$.addCallback', arguments); - var id = null; - if (!eventStr || !func || typeof func !== 'function') { - utils.logError('error registering callback. Check method signature'); - return id; - } - - id = utils.getUniqueIdentifierStr; - bidmanager.addCallback(id, func, eventStr); - return id; -}; - -/** - * Remove a callback event - * //@param {string} cbId id of the callback to remove - * @alias module:$$PREBID_GLOBAL$$.removeCallback - * @returns {string} id for callback - * - * @deprecated This function will be removed in Prebid 1.0 - * Please use offEvent instead. - */ -$$PREBID_GLOBAL$$.removeCallback = function (/* cbId */) { - // todo - utils.logWarn('$$PREBID_GLOBAL$$.removeCallback will be removed in Prebid 1.0. Please use offEvent instead.'); - return null; -}; - -/** +/* * Wrapper to register bidderAdapter externally (adaptermanager.registerBidAdapter()) * @param {Function} bidderAdaptor [description] * @param {string} bidderCode [description] + * @alias module:pbjs.registerBidAdapter */ $$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.registerBidAdapter', arguments); @@ -552,6 +451,7 @@ $$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { /** * Wrapper to register analyticsAdapter externally (adaptermanager.registerAnalyticsAdapter()) * @param {Object} options [description] + * @alias module:pbjs.registerAnalyticsAdapter */ $$PREBID_GLOBAL$$.registerAnalyticsAdapter = function (options) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.registerAnalyticsAdapter', arguments); @@ -562,22 +462,10 @@ $$PREBID_GLOBAL$$.registerAnalyticsAdapter = function (options) { } }; -$$PREBID_GLOBAL$$.bidsAvailableForAdapter = function (bidderCode) { - utils.logInfo('Invoking $$PREBID_GLOBAL$$.bidsAvailableForAdapter', arguments); - - $$PREBID_GLOBAL$$._bidsRequested.find(bidderRequest => bidderRequest.bidderCode === bidderCode).bids - .map(bid => { - return Object.assign(bid, bidfactory.createBid(1), { - bidderCode, - adUnitCode: bid.placementCode - }); - }) - .map(bid => $$PREBID_GLOBAL$$._bidsReceived.push(bid)); -}; - /** * Wrapper to bidfactory.createBid() * @param {string} statusCode [description] + * @alias module:pbjs.createBid * @return {Object} bidResponse [description] */ $$PREBID_GLOBAL$$.createBid = function (statusCode) { @@ -586,24 +474,10 @@ $$PREBID_GLOBAL$$.createBid = function (statusCode) { }; /** - * Wrapper to bidmanager.addBidResponse - * @param {string} adUnitCode [description] - * @param {Object} bid [description] - * - * @deprecated This function will be removed in Prebid 1.0 - * Each bidder will be passed a reference to addBidResponse function in callBids as an argument. - * See https://github.com/prebid/Prebid.js/issues/1087 for more details. - */ -$$PREBID_GLOBAL$$.addBidResponse = function (adUnitCode, bid) { - utils.logWarn('$$PREBID_GLOBAL$$.addBidResponse will be removed in Prebid 1.0. Each bidder will be passed a reference to addBidResponse function in callBids as an argument. See https://github.com/prebid/Prebid.js/issues/1087 for more details.'); - utils.logInfo('Invoking $$PREBID_GLOBAL$$.addBidResponse', arguments); - bidmanager.addBidResponse(adUnitCode, bid); -}; - -/** - * Wrapper to adloader.loadScript + * @deprecated this function will be removed in the next release. Prebid has deprected external JS loading. * @param {string} tagSrc [description] * @param {Function} callback [description] + * @alias module:pbjs.loadScript */ $$PREBID_GLOBAL$$.loadScript = function (tagSrc, callback, useCache) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.loadScript', arguments); @@ -617,11 +491,12 @@ $$PREBID_GLOBAL$$.loadScript = function (tagSrc, callback, useCache) { * For usage, see [Integrate with the Prebid Analytics * API](http://prebid.org/dev-docs/integrate-with-the-prebid-analytics-api.html). * - * For a list of supported analytics adapters, see [Analytics for + * For a list of analytics adapters, see [Analytics for * Prebid](http://prebid.org/overview/analytics.html). * @param {Object} config * @param {string} config.provider The name of the provider, e.g., `"ga"` for Google Analytics. * @param {Object} config.options The options for this particular analytics adapter. This will likely vary between adapters. + * @alias module:pbjs.enableAnalytics */ $$PREBID_GLOBAL$$.enableAnalytics = function (config) { if (config && !utils.isEmpty(config)) { @@ -632,6 +507,9 @@ $$PREBID_GLOBAL$$.enableAnalytics = function (config) { } }; +/** + * @alias module:pbjs.aliasBidder + */ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.aliasBidder', arguments); if (bidderCode && alias) { @@ -641,31 +519,6 @@ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias) { } }; -/** - * Sets a default price granularity scheme. - * @param {string|Object} granularity - the granularity scheme. - * @deprecated - use pbjs.setConfig({ priceGranularity: }) - * "low": $0.50 increments, capped at $5 CPM - * "medium": $0.10 increments, capped at $20 CPM (the default) - * "high": $0.01 increments, capped at $20 CPM - * "auto": Applies a sliding scale to determine granularity - * "dense": Like "auto", but the bid price granularity uses smaller increments, especially at lower CPMs - * - * Alternatively a custom object can be specified: - * { "buckets" : [{"min" : 0,"max" : 20,"increment" : 0.1,"cap" : true}]}; - * See http://prebid.org/dev-docs/publisher-api-reference.html#module_pbjs.setPriceGranularity for more details - */ -$$PREBID_GLOBAL$$.setPriceGranularity = function (granularity) { - utils.logWarn('$$PREBID_GLOBAL$$.setPriceGranularity will be removed in Prebid 1.0. Use $$PREBID_GLOBAL$$.setConfig({ priceGranularity: }) instead.') - utils.logInfo('Invoking $$PREBID_GLOBAL$$.setPriceGranularity', arguments); - config.setConfig({ priceGranularity: granularity }); -}; - -/** @deprecated - use pbjs.setConfig({ enableSendAllBids: }) */ -$$PREBID_GLOBAL$$.enableSendAllBids = function () { - config.setConfig({ enableSendAllBids: true }); -}; - /** * The bid response object returned by an external bidder adapter during the auction. * @typedef {Object} AdapterBidResponse @@ -702,109 +555,41 @@ $$PREBID_GLOBAL$$.enableSendAllBids = function () { */ /** - * Get all of the bids that have won their respective auctions. Useful for [troubleshooting your integration](http://prebid.org/dev-docs/prebid-troubleshooting-guide.html). - * @return {Array} A list of bids that have won their respective auctions. + * Get all of the bids that have been rendered. Useful for [troubleshooting your integration](http://prebid.org/dev-docs/prebid-troubleshooting-guide.html). + * @return {Array} A list of bids that have been rendered. */ $$PREBID_GLOBAL$$.getAllWinningBids = function () { - return $$PREBID_GLOBAL$$._winningBids; + return auctionManager.getAllWinningBids() + .map(removeRequestId); }; /** - * Build master video tag from publishers adserver tag - * @param {string} adserverTag default url - * @param {Object} options options for video tag - * - * @deprecated Include the dfpVideoSupport module in your build, and use the $$PREBID_GLOBAL$$.adservers.dfp.buildVideoAdUrl function instead. - * This function will be removed in Prebid 1.0. + * Get all of the bids that have won their respective auctions. + * @return {Array} A list of bids that have won their respective auctions. */ -$$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag = function (adserverTag, options) { - utils.logWarn('$$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag will be removed in Prebid 1.0. Include the dfpVideoSupport module in your build, and use the $$PREBID_GLOBAL$$.adservers.dfp.buildVideoAdUrl function instead'); - utils.logInfo('Invoking $$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag', arguments); - var urlComponents = parseURL(adserverTag); - - // return original adserverTag if no bids received - if ($$PREBID_GLOBAL$$._bidsReceived.length === 0) { - return adserverTag; - } - - var masterTag = ''; - if (options.adserver.toLowerCase() === 'dfp') { - var dfpAdserverObj = adserver.dfpAdserver(options, urlComponents); - if (!dfpAdserverObj.verifyAdserverTag()) { - utils.logError('Invalid adserverTag, required google params are missing in query string'); - } - dfpAdserverObj.appendQueryParams(); - masterTag = formatURL(dfpAdserverObj.urlComponents); - } else { - utils.logError('Only DFP adserver is supported'); - return; - } - return masterTag; +$$PREBID_GLOBAL$$.getAllPrebidWinningBids = function () { + return auctionManager.getBidsReceived() + .filter(bid => bid.status === BID_TARGETING_SET) + .map(removeRequestId); }; -/** - * Set the order bidders are called in. Valid values are: - * - * "fixed": Bidders will be called in the order in which they were defined within the adUnit.bids array. - * "random": Bidders will be called in random order. - * - * If never called, Prebid will use "random" as the default. - * - * @param {string} order One of the valid orders, described above. - * @deprecated - use pbjs.setConfig({ bidderSequence: }) - */ -$$PREBID_GLOBAL$$.setBidderSequence = adaptermanager.setBidderSequence; - /** * Get array of highest cpm bids for all adUnits, or highest cpm bid * object for the given adUnit * @param {string} adUnitCode - optional ad unit code + * @alias module:pbjs.getHighestCpmBids * @return {Array} array containing highest cpm bid object(s) */ $$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { - return targeting.getWinningBids(adUnitCode); -}; - -/** - * Set config for server to server header bidding - * @deprecated - use pbjs.setConfig({ s2sConfig: }) - * @typedef {Object} options - required - * @property {boolean} enabled enables S2S bidding - * @property {string[]} bidders bidders to request S2S - * === optional params below === - * @property {string} [endpoint] endpoint to contact - * @property {number} [timeout] timeout for S2S bidders - should be lower than `pbjs.requestBids({timeout})` - * @property {string} [adapter] adapter code to use for S2S - * @property {string} [syncEndpoint] endpoint URL for syncing cookies - * @property {boolean} [cookieSet] enables cookieSet functionality - */ -$$PREBID_GLOBAL$$.setS2SConfig = function(options) { - if (!utils.contains(Object.keys(options), 'accountId')) { - utils.logError('accountId missing in Server to Server config'); - return; - } - - if (!utils.contains(Object.keys(options), 'bidders')) { - utils.logError('bidders missing in Server to Server config'); - return; - } - - const config = Object.assign({ - enabled: false, - endpoint: CONSTANTS.S2S.DEFAULT_ENDPOINT, - timeout: 1000, - maxBids: 1, - adapter: CONSTANTS.S2S.ADAPTER, - syncEndpoint: CONSTANTS.S2S.SYNC_ENDPOINT, - cookieSet: true, - bidders: [] - }, options); - adaptermanager.setS2SConfig(config); + let bidsReceived = auctionManager.getBidsReceived().filter(getOldestBid); + return targeting.getWinningBids(adUnitCode, bidsReceived) + .map(removeRequestId); }; /** * Get Prebid config options * @param {Object} options + * @alias module:pbjs.getConfig */ $$PREBID_GLOBAL$$.getConfig = config.getConfig; @@ -841,6 +626,7 @@ $$PREBID_GLOBAL$$.getConfig = config.getConfig; * @param {string} options.publisherDomain The publisher's domain where Prebid is running, for cross-domain iFrame communication. Example: `pbjs.setConfig({ publisherDomain: "https://www.theverge.com" })`. * @param {number} options.cookieSyncDelay A delay (in milliseconds) for requesting cookie sync to stay out of the critical path of page load. Example: `pbjs.setConfig({ cookieSyncDelay: 100 })`. * @param {Object} options.s2sConfig The configuration object for [server-to-server header bidding](http://prebid.org/dev-docs/get-started-with-prebid-server.html). Example: + * @alias module:pbjs.setConfig * ``` * pbjs.setConfig({ * s2sConfig: { @@ -873,17 +659,17 @@ $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); * by prebid once it's done loading. If it runs after prebid loads, then this monkey-patch causes their * function to execute immediately. * - * @memberof $$PREBID_GLOBAL$$ + * @memberof pbjs * @param {function} command A function which takes no arguments. This is guaranteed to run exactly once, and only after * the Prebid script has been fully loaded. - * @alias module:$$PREBID_GLOBAL$$.cmd.push + * @alias module:pbjs.cmd.push */ $$PREBID_GLOBAL$$.cmd.push = function(command) { if (typeof command === 'function') { try { command.call(); } catch (e) { - utils.logError('Error processing command :' + e.message); + utils.logError('Error processing command :', e.message, e.stack); } } else { utils.logError('Commands written into $$PREBID_GLOBAL$$.cmd.push must be wrapped in a function'); @@ -905,6 +691,9 @@ function processQueue(queue) { }); } +/** + * @alias module:pbjs.processQueue + */ $$PREBID_GLOBAL$$.processQueue = function() { processQueue($$PREBID_GLOBAL$$.que); processQueue($$PREBID_GLOBAL$$.cmd); diff --git a/src/secureCreatives.js b/src/secureCreatives.js index efc1386fde3..424d1402831 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -6,6 +6,9 @@ import events from './events'; import { fireNativeTrackers } from './native'; import { EVENTS } from './constants'; +import { isSlotMatchingAdUnitCode } from './utils'; +import { auctionManager } from './auctionManager'; +import find from 'core-js/library/fn/array/find'; const BID_WON = EVENTS.BID_WON; @@ -23,7 +26,7 @@ function receiveMessage(ev) { } if (data.adId) { - const adObject = $$PREBID_GLOBAL$$._bidsReceived.find(function (bid) { + const adObject = find(auctionManager.getBidsReceived(), function (bid) { return bid.adId === data.adId; }); @@ -31,7 +34,7 @@ function receiveMessage(ev) { sendAdToCreative(adObject, data.adServerDomain, ev.source); // save winning bids - $$PREBID_GLOBAL$$._winningBids.push(adObject); + auctionManager.addWinningBid(adObject); events.emit(BID_WON, adObject); } @@ -43,7 +46,7 @@ function receiveMessage(ev) { // }), '*'); if (data.message === 'Prebid Native') { fireNativeTrackers(data, adObject); - $$PREBID_GLOBAL$$._winningBids.push(adObject); + auctionManager.addWinningBid(adObject); events.emit(BID_WON, adObject); } } @@ -66,11 +69,9 @@ function sendAdToCreative(adObject, remoteDomain, source) { } function resizeRemoteCreative({ adUnitCode, width, height }) { - const iframe = document.getElementById(window.googletag.pubads() - .getSlots().find(slot => { - return slot.getAdUnitPath() === adUnitCode || - slot.getSlotElementId() === adUnitCode; - }).getSlotElementId()).querySelector('iframe'); + const iframe = document.getElementById( + find(window.googletag.pubads().getSlots().filter(isSlotMatchingAdUnitCode(adUnitCode)), slot => slot) + .getSlotElementId()).querySelector('iframe'); iframe.width = '' + width; iframe.height = '' + height; diff --git a/src/sizeMapping.js b/src/sizeMapping.js index 9529c567308..5533c6b4efe 100644 --- a/src/sizeMapping.js +++ b/src/sizeMapping.js @@ -1,61 +1,89 @@ +import { config } from 'src/config'; +import { logWarn } from 'src/utils'; +import includes from 'core-js/library/fn/array/includes'; + +let sizeConfig = []; + /** - * @module sizeMapping + * @typedef {object} SizeConfig + * + * @property {string} [mediaQuery] A CSS media query string that will to be interpreted by window.matchMedia. If the + * media query matches then the this config will be active and sizesSupported will filter bid and adUnit sizes. If + * this property is not present then this SizeConfig will only be active if triggered manually by a call to + * pbjs.setConfig({labels:['label']) specifying one of the labels present on this SizeConfig. + * @property {Array} sizesSupported The sizes to be accepted if this SizeConfig is enabled. + * @property {Array} labels The active labels to match this SizeConfig to an adUnits and/or bidders. */ -import * as utils from './utils'; -let _win; - -function mapSizes(adUnit) { - if (!isSizeMappingValid(adUnit.sizeMapping)) { - return adUnit.sizes; - } - const width = getScreenWidth(); - if (!width) { - // size not detected - get largest value set for desktop - const mapping = adUnit.sizeMapping.reduce((prev, curr) => { - return prev.minWidth < curr.minWidth ? curr : prev; - }); - if (mapping.sizes && mapping.sizes.length) { - return mapping.sizes; - } - return adUnit.sizes; - } - let sizes = ''; - const mapping = adUnit.sizeMapping.find(sizeMapping => { - return width >= sizeMapping.minWidth; - }); - if (mapping && mapping.sizes && mapping.sizes.length) { - sizes = mapping.sizes; - utils.logMessage(`AdUnit : ${adUnit.code} resized based on device width to : ${sizes}`); - } else { - utils.logMessage(`AdUnit : ${adUnit.code} not mapped to any sizes for device width. This request will be suppressed.`); - } - return sizes; -} -function isSizeMappingValid(sizeMapping) { - if (utils.isArray(sizeMapping) && sizeMapping.length > 0) { - return true; - } - utils.logInfo('No size mapping defined'); - return false; +/** + * + * @param {Array} config + */ +export function setSizeConfig(config) { + sizeConfig = config; } +config.getConfig('sizeConfig', config => setSizeConfig(config.sizeConfig)); -function getScreenWidth(win) { - var w = win || _win || window; - var d = w.document; +/** + * Resolves the unique set of the union of all sizes and labels that are active from a SizeConfig.mediaQuery match + * @param {Array} labels Labels specified on adUnit or bidder + * @param {boolean} labelAll if true, all labels must match to be enabled + * @param {Array} activeLabels Labels passed in through requestBids + * @param {Array>} sizes Sizes specified on adUnit + * @param {Array} configs + * @returns {{labels: Array, sizes: Array>}} + */ +export function resolveStatus({labels = [], labelAll = false, activeLabels = []} = {}, sizes = [], configs = sizeConfig) { + let maps = evaluateSizeConfig(configs); - if (w.innerWidth) { - return w.innerWidth; - } else if (d.body.clientWidth) { - return d.body.clientWidth; - } else if (d.documentElement.clientWidth) { - return d.documentElement.clientWidth; + let filteredSizes; + if (maps.shouldFilter) { + filteredSizes = sizes.filter(size => maps.sizesSupported[size]); + } else { + filteredSizes = sizes; } - return 0; -} -function setWindow(win) { - _win = win; + return { + active: filteredSizes.length > 0 && ( + labels.length === 0 || ( + (!labelAll && ( + labels.some(label => maps.labels[label]) || + labels.some(label => includes(activeLabels, label)) + )) || + (labelAll && ( + labels.reduce((result, label) => !result ? result : ( + maps.labels[label] || includes(activeLabels, label) + ), true) + )) + ) + ), + sizes: filteredSizes + }; } -export { mapSizes, getScreenWidth, setWindow }; +function evaluateSizeConfig(configs) { + return configs.reduce((results, config) => { + if ( + typeof config === 'object' && + typeof config.mediaQuery === 'string' + ) { + if (matchMedia(config.mediaQuery).matches) { + if (Array.isArray(config.sizesSupported)) { + results.shouldFilter = true; + } + ['labels', 'sizesSupported'].forEach( + type => (config[type] || []).forEach( + thing => results[type][thing] = true + ) + ); + } + } else { + logWarn('sizeConfig rule missing required property "mediaQuery"'); + } + return results; + }, { + labels: {}, + sizesSupported: {}, + shouldFilter: false + }); +} diff --git a/src/targeting.js b/src/targeting.js index 3081100c8e4..0ca9f949a64 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -1,210 +1,385 @@ -import { uniques, isGptPubadsDefined, getHighestCpm, adUnitsFilter, groupBy } from './utils'; +import { uniques, isGptPubadsDefined, getHighestCpm, groupBy, isAdUnitCodeMatchingSlot, timestamp } from './utils'; import { config } from './config'; import { NATIVE_TARGETING_KEYS } from './native'; -const bidmanager = require('./bidmanager'); -const utils = require('./utils'); -var CONSTANTS = require('./constants'); +import { auctionManager } from './auctionManager'; +import includes from 'core-js/library/fn/array/includes'; + +const utils = require('./utils.js'); +var CONSTANTS = require('./constants.json'); -var targeting = exports; var pbTargetingKeys = []; -targeting.resetPresetTargeting = function(adUnitCode) { - if (isGptPubadsDefined()) { +export const BID_TARGETING_SET = 'targetingSet'; +export const RENDERED = 'rendered'; + +const MAX_DFP_KEYLENGTH = 20; +const TTL_BUFFER = 1000; + +// return unexpired bids +export const isBidExpired = (bid) => (bid.responseTimestamp + bid.ttl * 1000 + TTL_BUFFER) > timestamp(); + +// return bids whose status is not set. Winning bid can have status `targetingSet` or `rendered`. +const isUnusedBid = (bid) => bid && ((bid.status && !includes([BID_TARGETING_SET, RENDERED], bid.status)) || !bid.status); + +// If two bids are found for same adUnitCode, we will use the latest one to take part in auction +// This can happen in case of concurrent auctions +export const getOldestBid = function(bid, i, arr) { + let oldestBid = true; + arr.forEach((val, j) => { + if (i === j) return; + if (bid.bidder === val.bidder && bid.adUnitCode === val.adUnitCode && bid.responseTimestamp > val.responseTimestamp) { + oldestBid = false; + } + }); + return oldestBid; +} + +/** + * @typedef {Object.} targeting + * @property {string} targeting_key + */ + +/** + * @typedef {Object.[]>[]} targetingArray + */ + +export function newTargeting(auctionManager) { + let targeting = {}; + + targeting.resetPresetTargeting = function(adUnitCode) { + if (isGptPubadsDefined()) { + const adUnitCodes = getAdUnitCodes(adUnitCode); + const adUnits = auctionManager.getAdUnits().filter(adUnit => includes(adUnitCodes, adUnit.code)); + window.googletag.pubads().getSlots().forEach(slot => { + pbTargetingKeys.forEach(function(key) { + // reset only registered adunits + adUnits.forEach(function(unit) { + if (unit.code === slot.getAdUnitPath() || + unit.code === slot.getSlotElementId()) { + slot.setTargeting(key, null); + } + }); + }); + }); + } + }; + + /** + * Returns all ad server targeting for all ad units. + * @param {string=} adUnitCode + * @return {Object.} targeting + */ + targeting.getAllTargeting = function(adUnitCode, bidsReceived = getBidsReceived()) { const adUnitCodes = getAdUnitCodes(adUnitCode); - const adUnits = $$PREBID_GLOBAL$$.adUnits.filter(adUnit => adUnitCodes.includes(adUnit.code)); - window.googletag.pubads().getSlots().forEach(slot => { - pbTargetingKeys.forEach(function(key) { - // reset only registered adunits - adUnits.forEach(function(unit) { - if (unit.code === slot.getAdUnitPath() || - unit.code === slot.getSlotElementId()) { - slot.setTargeting(key, null); + + // Get targeting for the winning bid. Add targeting for any bids that have + // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. + var targeting = getWinningBidTargeting(adUnitCodes, bidsReceived) + .concat(getCustomBidTargeting(adUnitCodes, bidsReceived)) + .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes, bidsReceived) : []); + + // store a reference of the targeting keys + targeting.map(adUnitCode => { + Object.keys(adUnitCode).map(key => { + adUnitCode[key].map(targetKey => { + if (pbTargetingKeys.indexOf(Object.keys(targetKey)[0]) === -1) { + pbTargetingKeys = Object.keys(targetKey).concat(pbTargetingKeys); } }); }); }); + + targeting = flattenTargeting(targeting); + return targeting; + }; + + /** + * Converts targeting array and flattens to make it easily iteratable + * e.g: Sample input to this function + * ``` + * [ + * { + * "div-gpt-ad-1460505748561-0": [{"hb_bidder": ["appnexusAst"]}] + * }, + * { + * "div-gpt-ad-1460505748561-0": [{"hb_bidder_appnexusAs": ["appnexusAst"]}] + * } + * ] + * ``` + * Resulting array + * ``` + * { + * "div-gpt-ad-1460505748561-0": { + * "hb_bidder": "appnexusAst", + * "hb_bidder_appnexusAs": "appnexusAst" + * } + * } + * ``` + * + * @param {targetingArray} targeting + * @return {Object.} targeting + */ + function flattenTargeting(targeting) { + let targetingObj = targeting.map(targeting => { + return { + [Object.keys(targeting)[0]]: targeting[Object.keys(targeting)[0]] + .map(target => { + return { + [Object.keys(target)[0]]: target[Object.keys(target)[0]].join(', ') + }; + }).reduce((p, c) => Object.assign(c, p), {}) + }; + }).reduce(function (accumulator, targeting) { + var key = Object.keys(targeting)[0]; + accumulator[key] = Object.assign({}, accumulator[key], targeting[key]); + return accumulator; + }, {}); + return targetingObj; } -}; - -targeting.getAllTargeting = function(adUnitCode) { - const adUnitCodes = getAdUnitCodes(adUnitCode); - - // Get targeting for the winning bid. Add targeting for any bids that have - // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. - var targeting = getWinningBidTargeting(adUnitCodes) - .concat(getAlwaysUseBidTargeting(adUnitCodes)) - .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes) : []); - - // store a reference of the targeting keys - targeting.map(adUnitCode => { - Object.keys(adUnitCode).map(key => { - adUnitCode[key].map(targetKey => { - if (pbTargetingKeys.indexOf(Object.keys(targetKey)[0]) === -1) { - pbTargetingKeys = Object.keys(targetKey).concat(pbTargetingKeys); - } - }); - }); - }); - return targeting; -}; - -targeting.setTargeting = function(targetingConfig) { - window.googletag.pubads().getSlots().forEach(slot => { - targetingConfig.filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath() || - Object.keys(targeting)[0] === slot.getSlotElementId()) - .forEach(targeting => targeting[Object.keys(targeting)[0]] - .forEach(key => { - key[Object.keys(key)[0]] - .map((value) => { - utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${Object.keys(key)[0]} value: ${value}`); + + /** + * Sets targeting for DFP + * @param {Object.>} targetingConfig + */ + targeting.setTargetingForGPT = function(targetingConfig) { + window.googletag.pubads().getSlots().forEach(slot => { + Object.keys(targetingConfig).filter(isAdUnitCodeMatchingSlot(slot)) + .forEach(targetId => + Object.keys(targetingConfig[targetId]).forEach(key => { + let valueArr = targetingConfig[targetId][key].split(','); + valueArr = (valueArr.length > 1) ? [valueArr] : valueArr; + valueArr.map((value) => { + utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${key} value: ${value}`); return value; - }) - .forEach(value => { - slot.setTargeting(Object.keys(key)[0], value); + }).forEach(value => { + slot.setTargeting(key, value); }); - })); - }); -}; + }) + ) + }) + }; -/** - * normlizes input to a `adUnit.code` array - * @param {(string|string[])} adUnitCode [description] - * @return {string[]} AdUnit code array - */ -function getAdUnitCodes(adUnitCode) { - if (typeof adUnitCode === 'string') { - return [adUnitCode]; - } else if (utils.isArray(adUnitCode)) { - return adUnitCode; + /** + * normlizes input to a `adUnit.code` array + * @param {(string|string[])} adUnitCode [description] + * @return {string[]} AdUnit code array + */ + function getAdUnitCodes(adUnitCode) { + if (typeof adUnitCode === 'string') { + return [adUnitCode]; + } else if (utils.isArray(adUnitCode)) { + return adUnitCode; + } + return auctionManager.getAdUnitCodes() || []; } - return $$PREBID_GLOBAL$$._adUnitCodes || []; -} -/** - * Returns top bids for a given adUnit or set of adUnits. - * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes - * @return {[type]} [description] - */ -targeting.getWinningBids = function(adUnitCode) { - const adUnitCodes = getAdUnitCodes(adUnitCode); - - return $$PREBID_GLOBAL$$._bidsReceived - .filter(bid => adUnitCodes.includes(bid.adUnitCode)) - .filter(bid => bid.cpm > 0) - .map(bid => bid.adUnitCode) - .filter(uniques) - .map(adUnitCode => $$PREBID_GLOBAL$$._bidsReceived - .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) - .reduce(getHighestCpm, getEmptyBid(adUnitCode))); -}; - -targeting.setTargetingForAst = function() { - let targeting = $$PREBID_GLOBAL$$.getAdserverTargeting(); - Object.keys(targeting).forEach(targetId => - Object.keys(targeting[targetId]).forEach(key => { - utils.logMessage(`Attempting to set targeting for targetId: ${targetId} key: ${key} value: ${targeting[targetId][key]}`); - // setKeywords supports string and array as value - if (utils.isStr(targeting[targetId][key]) || utils.isArray(targeting[targetId][key])) { - let keywordsObj = {}; - let input = 'hb_adid'; - let nKey = (key.substring(0, input.length) === input) ? key.toUpperCase() : key; - keywordsObj[nKey] = targeting[targetId][key]; - window.apntag.setKeywords(targetId, keywordsObj); + function getBidsReceived() { + return auctionManager.getBidsReceived() + .filter(isUnusedBid) + .filter(exports.isBidExpired) + .filter(getOldestBid) + ; + } + + /** + * Returns top bids for a given adUnit or set of adUnits. + * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes + * @return {[type]} [description] + */ + targeting.getWinningBids = function(adUnitCode, bidsReceived = getBidsReceived()) { + const adUnitCodes = getAdUnitCodes(adUnitCode); + + return bidsReceived + .filter(bid => includes(adUnitCodes, bid.adUnitCode)) + .filter(bid => bid.cpm > 0) + .map(bid => bid.adUnitCode) + .filter(uniques) + .map(adUnitCode => bidsReceived + .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) + .reduce(getHighestCpm, getEmptyBid(adUnitCode))); + }; + + /** + * Sets targeting for AST + */ + targeting.setTargetingForAst = function() { + let astTargeting = targeting.getAllTargeting(); + Object.keys(astTargeting).forEach(targetId => + Object.keys(astTargeting[targetId]).forEach(key => { + utils.logMessage(`Attempting to set targeting for targetId: ${targetId} key: ${key} value: ${astTargeting[targetId][key]}`); + // setKeywords supports string and array as value + if (utils.isStr(astTargeting[targetId][key]) || utils.isArray(astTargeting[targetId][key])) { + let keywordsObj = {}; + keywordsObj[key.toUpperCase()] = astTargeting[targetId][key]; + window.apntag.setKeywords(targetId, keywordsObj); + } + }) + ); + }; + + /** + * Get targeting key value pairs for winning bid. + * @param {string[]} AdUnit code array + * @return {targetingArray} winning bids targeting + */ + function getWinningBidTargeting(adUnitCodes, bidsReceived) { + let winners = targeting.getWinningBids(adUnitCodes, bidsReceived); + winners.forEach((winner) => { + winner.status = BID_TARGETING_SET; + }); + + // TODO : Add losing bids to pool from here ? + let standardKeys = getStandardKeys(); + + winners = winners.map(winner => { + return { + [winner.adUnitCode]: Object.keys(winner.adserverTargeting) + .filter(key => + typeof winner.sendStandardTargeting === 'undefined' || + winner.sendStandardTargeting || + standardKeys.indexOf(key) === -1) + .map(key => ({ + [(key === 'hb_deal') ? `${key}_${winner.bidderCode}`.substring(0, MAX_DFP_KEYLENGTH) : key.substring(0, MAX_DFP_KEYLENGTH)]: [winner.adserverTargeting[key]] + })) + }; + }); + + return winners; + } + + function getStandardKeys() { + return auctionManager.getStandardBidderAdServerTargeting() // in case using a custom standard key set + .map(targeting => targeting.key) + .concat(CONSTANTS.TARGETING_KEYS).filter(uniques); // standard keys defined in the library. + } + + /** + * Merge custom adserverTargeting with same key name for same adUnitCode. + * e.g: Appnexus defining custom keyvalue pair foo:bar and Rubicon defining custom keyvalue pair foo:baz will be merged to foo: ['bar','baz'] + * + * @param {Object[]} acc Accumulator for reducer. It will store updated bidResponse objects + * @param {Object} bid BidResponse + * @param {number} index current index + * @param {Array} arr original array + */ + function mergeAdServerTargeting(acc, bid, index, arr) { + function concatTargetingValue(key) { + return function(currentBidElement) { + if (!utils.isArray(currentBidElement.adserverTargeting[key])) { + currentBidElement.adserverTargeting[key] = [currentBidElement.adserverTargeting[key]]; + } + currentBidElement.adserverTargeting[key] = currentBidElement.adserverTargeting[key].concat(bid.adserverTargeting[key]).filter(uniques); + delete bid.adserverTargeting[key]; } - }) - ); -}; + } -function getWinningBidTargeting(adUnitCodes) { - let winners = targeting.getWinningBids(adUnitCodes); - let standardKeys = getStandardKeys(); + function hasSameAdunitCodeAndKey(key) { + return function(currentBidElement) { + return currentBidElement.adUnitCode === bid.adUnitCode && currentBidElement.adserverTargeting[key] + } + } - winners = winners.map(winner => { - return { - [winner.adUnitCode]: Object.keys(winner.adserverTargeting) - .filter(key => - typeof winner.sendStandardTargeting === 'undefined' || - winner.sendStandardTargeting || - standardKeys.indexOf(key) === -1) - .map(key => ({ [key.substring(0, 20)]: [winner.adserverTargeting[key]] })) - }; - }); + Object.keys(bid.adserverTargeting) + .filter(getCustomKeys()) + .forEach(key => { + if (acc.length) { + acc.filter(hasSameAdunitCodeAndKey(key)) + .forEach(concatTargetingValue(key)); + } + }); + acc.push(bid); + return acc; + } - return winners; -} + function getCustomKeys() { + let standardKeys = getStandardKeys(); + return function(key) { + return standardKeys.indexOf(key) === -1; + } + } -function getStandardKeys() { - return bidmanager.getStandardBidderAdServerTargeting() // in case using a custom standard key set - .map(targeting => targeting.key) - .concat(CONSTANTS.TARGETING_KEYS).filter(uniques); // standard keys defined in the library. -} + function truncateCustomKeys(bid) { + return { + [bid.adUnitCode]: Object.keys(bid.adserverTargeting) + // Get only the non-standard keys of the losing bids, since we + // don't want to override the standard keys of the winning bid. + .filter(getCustomKeys()) + .map(key => { + return { + [key.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]] + }; + }) + } + } -/** - * Get custom targeting keys for bids that have `alwaysUseBid=true`. - */ -function getAlwaysUseBidTargeting(adUnitCodes) { - let standardKeys = getStandardKeys(); - return $$PREBID_GLOBAL$$._bidsReceived - .filter(adUnitsFilter.bind(this, adUnitCodes)) - .map(bid => { - if (bid.alwaysUseBid) { - return { - [bid.adUnitCode]: Object.keys(bid.adserverTargeting).map(key => { - // Get only the non-standard keys of the losing bids, since we - // don't want to override the standard keys of the winning bid. - if (standardKeys.indexOf(key) > -1) { - return; - } + /** + * Get custom targeting key value pairs for bids. + * @param {string[]} AdUnit code array + * @return {targetingArray} bids with custom targeting defined in bidderSettings + */ + function getCustomBidTargeting(adUnitCodes, bidsReceived) { + return bidsReceived + .filter(bid => includes(adUnitCodes, bid.adUnitCode)) + .map(bid => Object.assign({}, bid)) + .reduce(mergeAdServerTargeting, []) + .map(truncateCustomKeys) + .filter(bid => bid); // removes empty elements in array; + } - return { [key.substring(0, 20)]: [bid.adserverTargeting[key]] }; - }).filter(key => key) // remove empty elements + /** + * Get targeting key value pairs for non-winning bids. + * @param {string[]} AdUnit code array + * @return {targetingArray} all non-winning bids targeting + */ + function getBidLandscapeTargeting(adUnitCodes, bidsReceived) { + const standardKeys = CONSTANTS.TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS); + const bids = []; + // bucket by adUnitcode + let buckets = groupBy(bidsReceived, 'adUnitCode'); + // filter top bid for each bucket by bidder + Object.keys(buckets).forEach(bucketKey => { + let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); + Object.keys(bidsByBidder).forEach(key => bids.push(bidsByBidder[key].reduce(getHighestCpm, getEmptyBid()))); + }); + // populate targeting keys for the remaining bids + return bids.map(bid => { + if ( + bid.adserverTargeting && adUnitCodes && + ((utils.isArray(adUnitCodes) && includes(adUnitCodes, bid.adUnitCode)) || + (typeof adUnitCodes === 'string' && bid.adUnitCode === adUnitCodes)) + ) { + return { + [bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter( + key => typeof bid.adserverTargeting[key] !== 'undefined') + ) }; } - }) - .filter(bid => bid); // removes empty elements in array; -} + }).filter(bid => bid); // removes empty elements in array + } -function getBidLandscapeTargeting(adUnitCodes) { - const standardKeys = CONSTANTS.TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS); - const bids = []; - // bucket by adUnitcode - let buckets = groupBy($$PREBID_GLOBAL$$._bidsReceived, 'adUnitCode'); - // filter top bid for each bucket by bidder - Object.keys(buckets).forEach(bucketKey => { - let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); - Object.keys(bidsByBidder).forEach(key => bids.push(bidsByBidder[key].reduce(getHighestCpm, getEmptyBid()))); - }); - // populate targeting keys for the remaining bids - return bids.map(bid => { - if (bid.adserverTargeting) { + function getTargetingMap(bid, keys) { + return keys.map(key => { return { - [bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter( - key => typeof bid.adserverTargeting[key] !== 'undefined') - ) + [`${key}_${bid.bidderCode}`.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]] }; + }); + } + + targeting.isApntagDefined = function() { + if (window.apntag && utils.isFn(window.apntag.setKeywords)) { + return true; } - }).filter(bid => bid); // removes empty elements in array -} + }; -function getTargetingMap(bid, keys) { - return keys.map(key => { + function getEmptyBid(adUnitCode) { return { - [`${key}_${bid.bidderCode}`.substring(0, 20)]: [bid.adserverTargeting[key]] + adUnitCode: adUnitCode, + cpm: 0, + adserverTargeting: {}, + timeToRespond: 0 }; - }); -} - -targeting.isApntagDefined = function() { - if (window.apntag && utils.isFn(window.apntag.setKeywords)) { - return true; } -}; - -function getEmptyBid(adUnitCode) { - return { - adUnitCode: adUnitCode, - cpm: 0, - adserverTargeting: {}, - timeToRespond: 0 - }; + return targeting; } + +export const targeting = newTargeting(auctionManager); diff --git a/src/url.js b/src/url.js index 502245f3abd..c63bca2ca41 100644 --- a/src/url.js +++ b/src/url.js @@ -31,12 +31,15 @@ export function parse(url, options) { } else { parsed.href = decodeURIComponent(url); } + // in window.location 'search' is string, not object + let qsAsString = (options && 'decodeSearchAsString' in options && options.decodeSearchAsString); return { + href: parsed.href, protocol: (parsed.protocol || '').replace(/:$/, ''), hostname: parsed.hostname, port: +parsed.port, pathname: parsed.pathname.replace(/^(?!\/)/, '/'), - search: parseQS(parsed.search || ''), + search: (qsAsString) ? parsed.search : parseQS(parsed.search || ''), hash: (parsed.hash || '').replace(/^#/, ''), host: parsed.host || window.location.host }; diff --git a/src/userSync.js b/src/userSync.js index 8fb8c04cd24..059d3ea62c1 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -1,6 +1,16 @@ import * as utils from 'src/utils'; import { config } from 'src/config'; +// Set userSync default values +config.setDefaults({ + 'userSync': { + syncEnabled: true, + pixelEnabled: true, + syncsPerBidder: 5, + syncDelay: 3000 + } +}); + /** * Factory function which creates a new UserSyncPool. * @@ -136,7 +146,7 @@ export function newUserSync(userSyncDependencies) { return utils.logWarn(`Bidder is required for registering sync`); } if (Number(numAdapterBids[bidder]) >= usConfig.syncsPerBidder) { - return utils.logWarn(`Number of user syncs exceeded for "{$bidder}"`); + return utils.logWarn(`Number of user syncs exceeded for "${bidder}"`); } // All bidders are enabled by default. If specified only register for enabled bidders. let hasEnabledBidders = usConfig.enabledBidders && usConfig.enabledBidders.length; @@ -155,7 +165,7 @@ export function newUserSync(userSyncDependencies) { */ publicApi.syncUsers = (timeout = 0) => { if (timeout) { - return window.setTimeout(fireSyncs, Number(timeout)); + return setTimeout(fireSyncs, Number(timeout)); } fireSyncs(); }; diff --git a/src/utils.js b/src/utils.js index 00a06fcb091..cf977124dd1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,9 @@ import { config } from './config'; -var CONSTANTS = require('./constants'); +import clone from 'just-clone'; +import find from 'core-js/library/fn/array/find'; +import includes from 'core-js/library/fn/array/includes'; +import { parse } from './url'; +const CONSTANTS = require('./constants'); var _loggingChecked = false; @@ -7,6 +11,7 @@ var t_Arr = 'Array'; var t_Str = 'String'; var t_Fn = 'Function'; var t_Numb = 'Number'; +var t_Object = 'Object'; var toString = Object.prototype.toString; let infoLogger = null; try { @@ -154,17 +159,55 @@ export function parseGPTSingleSizeArray(singleSize) { } }; -exports.getTopWindowLocation = function () { - let location; +exports.getTopWindowLocation = function() { + if (exports.inIframe()) { + let loc; + try { + loc = exports.getAncestorOrigins() || exports.getTopFrameReferrer(); + } catch (e) { + logInfo('could not obtain top window location', e); + } + if (loc) return parse(loc, {'decodeSearchAsString': true}); + } + return exports.getWindowLocation(); +} + +exports.getTopFrameReferrer = function () { try { - // force an exception in x-domain enviornments. #1509 + // force an exception in x-domain environments. #1509 window.top.location.toString(); - location = window.top.location; + let referrerLoc = ''; + let currentWindow; + do { + currentWindow = currentWindow ? currentWindow.parent : window; + if (currentWindow.document && currentWindow.document.referrer) { + referrerLoc = currentWindow.document.referrer; + } + } + while (currentWindow !== window.top); + return referrerLoc; } catch (e) { - location = window.location; + return window.document.referrer; } +}; - return location; +exports.getAncestorOrigins = function () { + if (window.document.location && window.document.location.ancestorOrigins && + window.document.location.ancestorOrigins.length >= 1) { + return window.document.location.ancestorOrigins[window.document.location.ancestorOrigins.length - 1]; + } +}; + +exports.getWindowTop = function () { + return window.top; +}; + +exports.getWindowSelf = function () { + return window.self; +}; + +exports.getWindowLocation = function () { + return window.location; }; exports.getTopWindowUrl = function () { @@ -178,6 +221,14 @@ exports.getTopWindowUrl = function () { return href; }; +exports.getTopWindowReferrer = function() { + try { + return window.top.document.referrer; + } catch (e) { + return document.referrer; + } +}; + exports.logWarn = function (msg) { if (debugTurnedOn() && console.warn) { console.warn('WARNING: ' + msg); @@ -206,12 +257,11 @@ function hasConsoleLogger() { return (window.console && window.console.log); } -exports.hasConsoleLogger = hasConsoleLogger; +function hasConsoleError() { + return (window.console && window.console.error); +} -var errLogFn = (function (hasLogger) { - if (!hasLogger) return ''; - return window.console.error ? 'error' : 'log'; -}(hasConsoleLogger())); +exports.hasConsoleLogger = hasConsoleLogger; var debugTurnedOn = function () { if (config.getConfig('debug') === false && _loggingChecked === false) { @@ -225,10 +275,12 @@ var debugTurnedOn = function () { exports.debugTurnedOn = debugTurnedOn; -exports.logError = function (msg, code, exception) { - var errCode = code || 'ERROR'; - if (debugTurnedOn() && hasConsoleLogger()) { - console[errLogFn](console, errCode + ': ' + msg, exception || ''); +/** + * Wrapper to console.error. Takes N arguments to log the same as console.error. + */ +exports.logError = function () { + if (debugTurnedOn() && hasConsoleError()) { + console.error.apply(console, arguments); } }; @@ -331,6 +383,10 @@ exports.isNumber = function(object) { return this.isA(object, t_Numb); }; +exports.isPlainObject = function(object) { + return this.isA(object, t_Object); +} + /** * Return if the object is "empty"; * this includes falsey, no keys, or no items at indices @@ -339,7 +395,7 @@ exports.isNumber = function(object) { */ exports.isEmpty = function (object) { if (!object) return true; - if (this.isArray(object) || this.isStr(object)) { + if (exports.isArray(object) || exports.isStr(object)) { return !(object.length > 0); } @@ -457,6 +513,44 @@ exports.triggerPixel = function (url) { img.src = url; }; +exports.callBurl = function({ source, burl }) { + if (source === CONSTANTS.S2S.SRC && burl) { + exports.triggerPixel(burl); + } +}; + +/** + * Inserts an empty iframe with the specified `html`, primarily used for tracking purposes + * (though could be for other purposes) + * @param {string} htmlCode snippet of HTML code used for tracking purposes + */ +exports.insertHtmlIntoIframe = function(htmlCode) { + if (!htmlCode) { + return; + } + + let iframe = document.createElement('iframe'); + iframe.id = exports.getUniqueIdentifierStr(); + iframe.width = 0; + iframe.height = 0; + iframe.hspace = '0'; + iframe.vspace = '0'; + iframe.marginWidth = '0'; + iframe.marginHeight = '0'; + iframe.style.display = 'none'; + iframe.style.height = '0px'; + iframe.style.width = '0px'; + iframe.scrolling = 'no'; + iframe.frameBorder = '0'; + iframe.allowtransparency = 'true'; + + exports.insertElement(iframe, document, 'body'); + + iframe.contentWindow.document.open(); + iframe.contentWindow.document.write(htmlCode); + iframe.contentWindow.document.close(); +} + /** * Inserts empty iframe with the specified `url` for cookie sync * @param {string} url URL to be requested @@ -509,7 +603,7 @@ exports.createTrackPixelIframeHtml = function (url, encodeUri = true, sandbox = allowtransparency="true" marginheight="0" marginwidth="0" width="0" hspace="0" vspace="0" height="0" - style="height:0p;width:0p;display:none;" + style="height:0px;width:0px;display:none;" scrolling="no" src="${url}"> `; @@ -562,8 +656,8 @@ export function flatten(a, b) { return a.concat(b); } -export function getBidRequest(id) { - return $$PREBID_GLOBAL$$._bidsRequested.map(bidSet => bidSet.bids.find(bid => bid.bidId === id)).find(bid => bid); +export function getBidRequest(id, bidsRequested) { + return find(bidsRequested.map(bidSet => find(bidSet.bids, bid => bid.bidId === id)), bid => bid); } export function getKeys(obj) { @@ -621,7 +715,7 @@ export function shuffle(array) { } export function adUnitsFilter(filter, bid) { - return filter.includes((bid && bid.placementCode) || (bid && bid.adUnitCode)); + return includes(filter, bid && bid.adUnitCode); } /** @@ -634,13 +728,13 @@ export function isSrcdocSupported(doc) { 'srcdoc' in doc.defaultView.frameElement && !/firefox/i.test(navigator.userAgent); } -export function cloneJson(obj) { - return JSON.parse(JSON.stringify(obj)); +export function deepClone(obj) { + return clone(obj); } export function inIframe() { try { - return window.self !== window.top; + return exports.getWindowSelf() !== exports.getWindowTop(); } catch (e) { return true; } @@ -655,19 +749,17 @@ export function replaceAuctionPrice(str, cpm) { return str.replace(/\$\{AUCTION_PRICE\}/g, cpm); } -export function getBidderRequestAllAdUnits(bidder) { - return $$PREBID_GLOBAL$$._bidsRequested.find(request => request.bidderCode === bidder); +export function timestamp() { + return new Date().getTime(); } -export function getBidderRequest(bidder, adUnitCode) { - return $$PREBID_GLOBAL$$._bidsRequested.find(request => { - return request.bids - .filter(bid => bid.bidder === bidder && bid.placementCode === adUnitCode).length > 0; - }) || { start: null, requestId: null }; +export function checkCookieSupport() { + if (window.navigator.cookieEnabled || !!document.cookie.length) { + return true; + } } - export function cookiesAreEnabled() { - if (window.navigator.cookieEnabled || !!document.cookie.length) { + if (exports.checkCookieSupport()) { return true; } window.document.cookie = 'prebid.cookieTest'; @@ -720,6 +812,9 @@ export function groupBy(xs, key) { * @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]]; @@ -730,6 +825,19 @@ export function deepAccess(obj, path) { 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 + * and are macros left to be replaced if required + */ +export function createContentToExecuteExtScriptInFriendlyFrame(url) { + if (!url) { + return ''; + } + + return ``; +} + /** * Build an object consisting of only defined parameters to avoid creating an * object with defined keys and undefined values. @@ -761,13 +869,131 @@ export function isValidMediaTypes(mediaTypes) { const types = Object.keys(mediaTypes); - if (!types.every(type => SUPPORTED_MEDIA_TYPES.includes(type))) { + if (!types.every(type => includes(SUPPORTED_MEDIA_TYPES, type))) { return false; } if (mediaTypes.video && mediaTypes.video.context) { - return SUPPORTED_STREAM_TYPES.includes(mediaTypes.video.context); + return includes(SUPPORTED_STREAM_TYPES, mediaTypes.video.context); } return true; } + +export function getBidderRequest(bidRequests, bidder, adUnitCode) { + return find(bidRequests, request => { + return request.bids + .filter(bid => bid.bidder === bidder && bid.adUnitCode === adUnitCode).length > 0; + }) || { start: null, auctionId: null }; +} +/** + * Returns user configured bidder params from adunit + * @param {object} adunits + * @param {string} adunit code + * @param {string} bidder code + * @return {Array} user configured param for the given bidder adunit configuration + */ +export function getUserConfiguredParams(adUnits, adUnitCode, bidder) { + return adUnits + .filter(adUnit => adUnit.code === adUnitCode) + .map((adUnit) => adUnit.bids) + .reduce(flatten, []) + .filter((bidderData) => bidderData.bidder === bidder) + .map((bidderData) => bidderData.params || {}); +} +/** + * Returns the origin + */ +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 : ''); + } else { + return window.location.origin; + } +} + +/** + * Returns Do Not Track state + */ +export function getDNT() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNotTrack === '1' || navigator.doNotTrack === 'yes'; +} + +const compareCodeAndSlot = (slot, adUnitCode) => slot.getAdUnitPath() === adUnitCode || slot.getSlotElementId() === adUnitCode; + +/** + * Returns filter function to match adUnitCode in slot + * @param {object} slot GoogleTag slot + * @return {function} filter function + */ +export function isAdUnitCodeMatchingSlot(slot) { + return (adUnitCode) => compareCodeAndSlot(slot, adUnitCode); +} + +/** + * Returns filter function to match adUnitCode in slot + * @param {string} adUnitCode AdUnit code + * @return {function} filter function + */ +export function isSlotMatchingAdUnitCode(adUnitCode) { + return (slot) => compareCodeAndSlot(slot, adUnitCode); +} + +/** + * Constructs warning message for when unsupported bidders are dropped from an adunit + * @param {Object} adUnit ad unit from which the bidder is being dropped + * @param {string} bidder bidder code that is not compatible with the adUnit + * @return {string} warning message to display when condition is met + */ +export function unsupportedBidderMessage(adUnit, bidder) { + const mediaType = Object.keys(adUnit.mediaTypes || {'banner': 'banner'}).join(', '); + + return ` + ${adUnit.code} is a ${mediaType} ad unit + containing bidders that don't support ${mediaType}: ${bidder}. + This bidder won't fetch demand. + `; +} + +/** + * Delete property from object + * @param {Object} object + * @param {string} prop + * @return {Object} object + */ +export function deletePropertyFromObject(object, prop) { + 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'); +} + +/** + * Checks input is integer or not + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger + * @param {*} value + */ +export function isInteger(value) { + if (Number.isInteger) { + return Number.isInteger(value); + } else { + return typeof value === 'number' && isFinite(value) && Math.floor(value) === value; + } +} + +/** + * Converts a string value in camel-case to underscore eg 'placementId' becomes 'placement_id' + * @param {string} value string value to convert + */ +export function convertCamelToUnderscore(value) { + return value.replace(/(?:^|\.?)([A-Z])/g, function (x, y) { return '_' + y.toLowerCase() }).replace(/^_/, ''); +} diff --git a/src/video.js b/src/video.js index 386b6b692e9..8e0775a6d62 100644 --- a/src/video.js +++ b/src/video.js @@ -1,5 +1,7 @@ import { videoAdapters } from './adaptermanager'; -import { getBidRequest, deepAccess } from './utils'; +import { getBidRequest, deepAccess, logError } from './utils'; +import { config } from '../src/config'; +import includes from 'core-js/library/fn/array/includes'; const VIDEO_MEDIA_TYPE = 'video'; const OUTSTREAM = 'outstream'; @@ -7,10 +9,14 @@ const OUTSTREAM = 'outstream'; /** * Helper functions for working with video-enabled adUnits */ -export const videoAdUnit = adUnit => adUnit.mediaType === VIDEO_MEDIA_TYPE; -const nonVideoBidder = bid => !videoAdapters.includes(bid.bidder); +export const videoAdUnit = adUnit => { + const mediaType = adUnit.mediaType === VIDEO_MEDIA_TYPE; + const mediaTypes = deepAccess(adUnit, 'mediaTypes.video'); + return mediaType || mediaTypes; +}; +export const videoBidder = bid => includes(videoAdapters, bid.bidder); export const hasNonVideoBidder = adUnit => - adUnit.bids.filter(nonVideoBidder).length; + adUnit.bids.filter(bid => !videoBidder(bid)).length; /** * @typedef {object} VideoBid @@ -19,11 +25,12 @@ export const hasNonVideoBidder = adUnit => /** * Validate that the assets required for video context are present on the bid - * @param {VideoBid} bid video bid to validate - * @return {boolean} If object is valid + * @param {VideoBid} bid Video bid to validate + * @param {BidRequest[]} bidRequests All bid requests for an auction + * @return {Boolean} If object is valid */ -export function isValidVideoBid(bid) { - const bidRequest = getBidRequest(bid.adId); +export function isValidVideoBid(bid, bidRequests) { + const bidRequest = getBidRequest(bid.adId, bidRequests); const videoMediaType = bidRequest && deepAccess(bidRequest, 'mediaTypes.video'); @@ -32,6 +39,15 @@ export function isValidVideoBid(bid) { // if context not defined assume default 'instream' for video bids // instream bids require a vast url or vast xml content 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: "..."} }); + `); + return false; + } + return !!(bid.vastUrl || bid.vastXml); } diff --git a/src/videoCache.js b/src/videoCache.js index fe126fad6e0..cec2a3ec864 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -10,8 +10,7 @@ */ import { ajax } from './ajax'; - -const BASE_URL = 'https://prebid.adnxs.com/pbc/v1/cache' +import { config } from '../src/config'; /** * @typedef {object} CacheableUrlBid @@ -33,18 +32,20 @@ const BASE_URL = 'https://prebid.adnxs.com/pbc/v1/cache' * Function which wraps a URI that serves VAST XML, so that it can be loaded. * * @param {string} uri The URI where the VAST content can be found. + * @param {string} impUrl An impression tracker URL for the delivery of the video ad * @return A VAST URL which loads XML from the given URI. */ -function wrapURI(uri) { +function wrapURI(uri, impUrl) { // Technically, this is vulnerable to cross-script injection by sketchy vastUrl bids. // We could make sure it's a valid URI... but since we're loading VAST XML from the // URL they provide anyway, that's probably not a big deal. + let vastImp = (impUrl) ? `` : ``; return ` prebid.org wrapper - + ${vastImp} @@ -58,7 +59,7 @@ function wrapURI(uri) { * @param {CacheableBid} bid */ function toStorageRequest(bid) { - const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl); + const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl); return { type: 'xml', value: vastValue @@ -119,12 +120,12 @@ export function store(bids, done) { puts: bids.map(toStorageRequest) }; - ajax(BASE_URL, shimStorageCallback(done), JSON.stringify(requestData), { + ajax(config.getConfig('cache.url'), shimStorageCallback(done), JSON.stringify(requestData), { contentType: 'text/plain', withCredentials: true }); } export function getCacheUrl(id) { - return `${BASE_URL}?uuid=${id}`; + return `${config.getConfig('cache.url')}?uuid=${id}`; } diff --git a/test/.eslintrc.js b/test/.eslintrc.js index e41903540a1..4cd5a854ac4 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -29,21 +29,12 @@ module.exports = { "no-global-assign": "off", "no-path-concat": "off", "no-redeclare": "off", - "no-new-object": "off", - "no-array-constructor": "off", "node/no-deprecated-api": "off", - "no-cond-assign": "off", - "no-sequences": "off", - "no-eval": "off", - "no-new": "off", "no-return-assign": "off", "no-undef": "off", "no-unused-vars": "off", "no-use-before-define": "off", "no-useless-escape": "off", "one-var": "off", - "standard/array-bracket-even-spacing": "off", - "standard/no-callback-literal": "off", - "standard/object-curly-even-spacing": "off" } }; diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index 8108da3c555..fc59d7eeab3 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -4,7 +4,7 @@ export function getBidRequests() { return [ { 'bidderCode': 'appnexus', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'bidderRequestId': '2946b569352ef2', 'bids': [ { @@ -26,7 +26,7 @@ export function getBidRequests() { ], 'bidId': '392b5a6b05d648', 'bidderRequestId': '2946b569352ef2', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'startTime': 1462918897462, 'status': 1, 'transactionId': 'fsafsa' @@ -49,7 +49,7 @@ export function getBidRequests() { ], 'bidId': '4dccdc37746135', 'bidderRequestId': '2946b569352ef2', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'startTime': 1462918897463, 'status': 1, 'transactionId': 'fsafsa' @@ -59,7 +59,7 @@ export function getBidRequests() { }, { 'bidderCode': 'pubmatic', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'bidderRequestId': '5e1525bae3eb11', 'bids': [ { @@ -81,7 +81,7 @@ export function getBidRequests() { ], 'bidId': '6d11aa2d5b3659', 'bidderRequestId': '5e1525bae3eb11', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'transactionId': 'fsafsa' } ], @@ -89,7 +89,7 @@ export function getBidRequests() { }, { 'bidderCode': 'rubicon', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'bidderRequestId': '8778750ee15a77', 'bids': [ { @@ -130,7 +130,7 @@ export function getBidRequests() { ], 'bidId': '96aff279720d39', 'bidderRequestId': '8778750ee15a77', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'transactionId': 'fsafsa' } ], @@ -138,7 +138,7 @@ export function getBidRequests() { }, { 'bidderCode': 'triplelift', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'bidderRequestId': '107f5e6e98dcf09', 'bids': [ { @@ -159,7 +159,7 @@ export function getBidRequests() { ], 'bidId': '1144e2f0de84363', 'bidderRequestId': '107f5e6e98dcf09', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'startTime': 1462918897477, 'transactionId': 'fsafsa' } @@ -168,7 +168,7 @@ export function getBidRequests() { }, { 'bidderCode': 'brightcom', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'bidderRequestId': '12eeded736650b4', 'bids': [ { @@ -189,7 +189,7 @@ export function getBidRequests() { ], 'bidId': '135e89c039705da', 'bidderRequestId': '12eeded736650b4', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'status': 1, 'transactionId': 'fsafsa' } @@ -198,7 +198,7 @@ export function getBidRequests() { }, { 'bidderCode': 'brealtime', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'bidderRequestId': '167c4d79b615948', 'bids': [ { @@ -219,7 +219,7 @@ export function getBidRequests() { ], 'bidId': '17dd1d869bed44e', 'bidderRequestId': '167c4d79b615948', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'startTime': 1462918897480, 'status': 1, 'transactionId': 'fsafsa' @@ -229,7 +229,7 @@ export function getBidRequests() { }, { 'bidderCode': 'pagescience', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'bidderRequestId': '18bed198c172a69', 'bids': [ { @@ -250,7 +250,7 @@ export function getBidRequests() { ], 'bidId': '192c8c1df0f5d1d', 'bidderRequestId': '18bed198c172a69', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'startTime': 1462918897481, 'status': 1, 'transactionId': 'fsafsa' @@ -260,7 +260,7 @@ export function getBidRequests() { }, { 'bidderCode': 'amazon', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'bidderRequestId': '20d0d30333715a7', 'bids': [ { @@ -281,7 +281,7 @@ export function getBidRequests() { ], 'bidId': '21ae8131ec04f6e', 'bidderRequestId': '20d0d30333715a7', - 'requestId': '1863e370099523', + 'auctionId': '1863e370099523', 'transactionId': 'fsafsa' } ], @@ -310,14 +310,17 @@ export function getBidResponses() { 'pbHg': '0.11', 'pbAg': '0.10', 'size': '0x0', - 'requestId': 123456, + 'auctionId': 123456, 'adserverTargeting': { 'hb_bidder': 'triplelift', 'hb_adid': '222bb26f9e8bd', 'hb_pb': '10.00', 'hb_size': '0x0', 'foobar': '0x0' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'appnexus', @@ -339,14 +342,17 @@ export function getBidResponses() { 'pbAg': '10.00', 'size': '300x250', 'alwaysUseBid': true, - 'requestId': 123456, + 'auctionId': 123456, 'adserverTargeting': { 'hb_bidder': 'appnexus', 'hb_adid': '233bcbee889d46d', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'appnexus', @@ -368,14 +374,17 @@ export function getBidResponses() { 'pbAg': '10.00', 'size': '728x90', 'alwaysUseBid': true, - 'requestId': 123456, + 'auctionId': 123456, 'adserverTargeting': { 'hb_bidder': 'appnexus', 'hb_adid': '24bd938435ec3fc', 'hb_pb': '10.00', 'hb_size': '728x90', 'foobar': '728x90' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'pagescience', @@ -396,14 +405,17 @@ export function getBidResponses() { 'pbHg': '0.50', 'pbAg': '0.50', 'size': '300x250', - 'requestId': 123456, + 'auctionId': 123456, 'adserverTargeting': { 'hb_bidder': 'pagescience', 'hb_adid': '25bedd4813632d7', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'brightcom', @@ -423,14 +435,17 @@ export function getBidResponses() { 'pbHg': '0.17', 'pbAg': '0.15', 'size': '300x250', - 'requestId': 654321, + 'auctionId': 654321, 'adserverTargeting': { 'hb_bidder': 'brightcom', 'hb_adid': '26e0795ab963896', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'brealtime', @@ -451,14 +466,17 @@ export function getBidResponses() { 'pbHg': '0.50', 'pbAg': '0.50', 'size': '300x250', - 'requestId': 654321, + 'auctionId': 654321, 'adserverTargeting': { 'hb_bidder': 'brealtime', 'hb_adid': '275bd666f5a5a5d', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'pubmatic', @@ -480,14 +498,17 @@ export function getBidResponses() { 'pbHg': '5.93', 'pbAg': '5.90', 'size': '300x250', - 'requestId': 654321, + 'auctionId': 654321, 'adserverTargeting': { 'hb_bidder': 'pubmatic', 'hb_adid': '28f4039c636b6a7', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'rubicon', @@ -507,14 +528,17 @@ export function getBidResponses() { 'pbHg': '2.74', 'pbAg': '2.70', 'size': '300x600', - 'requestId': 654321, + 'auctionId': 654321, 'adserverTargeting': { 'hb_bidder': 'rubicon', 'hb_adid': '29019e2ab586a5a', 'hb_pb': '10.00', 'hb_size': '300x600', 'foobar': '300x600' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 } ]; } @@ -585,7 +609,7 @@ export function getAdUnits() { ], 'bidId': '3692954f816efc', 'bidderRequestId': '2b1a75d5e826c4', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'appnexus', @@ -606,7 +630,7 @@ export function getAdUnits() { ], 'bidId': '68136e1c47023d', 'bidderRequestId': '55e24a66bed717', - 'requestId': '1ff753bd4ae5cb', + 'auctionId': '1ff753bd4ae5cb', 'startTime': 1463510220995, 'status': 1 } @@ -643,7 +667,7 @@ export function getAdUnits() { ], 'bidId': '7e5d6af25ed188', 'bidderRequestId': '55e24a66bed717', - 'requestId': '1ff753bd4ae5cb', + 'auctionId': '1ff753bd4ae5cb', 'startTime': 1463510220996 }, { @@ -665,7 +689,7 @@ export function getAdUnits() { ], 'bidId': '4448d80ac1374e', 'bidderRequestId': '2b1a75d5e826c4', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'triplelift', @@ -685,7 +709,7 @@ export function getAdUnits() { ], 'bidId': '9514d586c52abf', 'bidderRequestId': '8c4f03b838d7ee', - 'requestId': '1ff753bd4ae5cb', + 'auctionId': '1ff753bd4ae5cb', 'startTime': 1463510220997 }, { @@ -708,7 +732,7 @@ export function getAdUnits() { ], 'bidId': '113079fed03f58c', 'bidderRequestId': '1048e0df882e965', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'rubicon', @@ -748,7 +772,7 @@ export function getAdUnits() { ], 'bidId': '13c2c2a79d155ea', 'bidderRequestId': '129e383ac549e5d', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'openx', @@ -769,7 +793,7 @@ export function getAdUnits() { ], 'bidId': '154f9cbf82df565', 'bidderRequestId': '1448569c2453b84', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'pubmatic', @@ -790,7 +814,7 @@ export function getAdUnits() { ], 'bidId': '17f8c3a8fb13308', 'bidderRequestId': '16095445eeb05e4', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'pagescience', @@ -810,7 +834,7 @@ export function getAdUnits() { ], 'bidId': '2074d5757675542', 'bidderRequestId': '19883380ef5453a', - 'requestId': '1ff753bd4ae5cb', + 'auctionId': '1ff753bd4ae5cb', 'startTime': 1463510221014 }, { @@ -831,7 +855,7 @@ export function getAdUnits() { ], 'bidId': '222b6ad5a9b835d', 'bidderRequestId': '2163409fdf6f333', - 'requestId': '1ff753bd4ae5cb', + 'auctionId': '1ff753bd4ae5cb', 'startTime': 1463510221015 }, { @@ -854,7 +878,7 @@ export function getAdUnits() { ], 'bidId': '2499961ab3f937a', 'bidderRequestId': '23b57a2de4ae50b', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'adform', @@ -876,7 +900,7 @@ export function getAdUnits() { ], 'bidId': '26605265bf5e9c5', 'bidderRequestId': '25a0902299c17d3', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'amazon', @@ -896,7 +920,7 @@ export function getAdUnits() { ], 'bidId': '2935d8f6764fe45', 'bidderRequestId': '28afa21ca9246c1', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'aol', @@ -917,7 +941,7 @@ export function getAdUnits() { ], 'bidId': '31d1489681dc539', 'bidderRequestId': '30bf32da9080fdd', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'sovrn', @@ -937,7 +961,7 @@ export function getAdUnits() { ], 'bidId': '33c1a8028d91563', 'bidderRequestId': '324bcb47cfcf034', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'pulsepoint', @@ -959,7 +983,7 @@ export function getAdUnits() { ], 'bidId': '379219f0506a26f', 'bidderRequestId': '360ec66bbb0719c', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' }, { 'bidder': 'brightcom', @@ -979,7 +1003,7 @@ export function getAdUnits() { ], 'bidId': '395cfcf496e7d6d', 'bidderRequestId': '38a776c7f001ea', - 'requestId': '1ff753bd4ae5cb' + 'auctionId': '1ff753bd4ae5cb' } ] } @@ -1008,14 +1032,17 @@ export function getBidResponsesFromAPI() { 'pbHg': '0.17', 'pbAg': '0.15', 'size': '300x250', - 'requestId': 654321, + 'auctionId': 654321, 'adserverTargeting': { 'hb_bidder': 'brightcom', 'hb_adid': '26e0795ab963896', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'brealtime', @@ -1036,14 +1063,17 @@ export function getBidResponsesFromAPI() { 'pbHg': '0.50', 'pbAg': '0.50', 'size': '300x250', - 'requestId': 654321, + 'auctionId': 654321, 'adserverTargeting': { 'hb_bidder': 'brealtime', 'hb_adid': '275bd666f5a5a5d', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'pubmatic', @@ -1065,14 +1095,17 @@ export function getBidResponsesFromAPI() { 'pbHg': '5.93', 'pbAg': '5.90', 'size': '300x250', - 'requestId': 654321, + 'auctionId': 654321, 'adserverTargeting': { 'hb_bidder': 'pubmatic', 'hb_adid': '28f4039c636b6a7', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }, { 'bidderCode': 'rubicon', @@ -1092,14 +1125,17 @@ export function getBidResponsesFromAPI() { 'pbHg': '2.74', 'pbAg': '2.70', 'size': '300x600', - 'requestId': 654321, + 'auctionId': 654321, 'adserverTargeting': { 'hb_bidder': 'rubicon', 'hb_adid': '29019e2ab586a5a', 'hb_pb': '10.00', 'hb_size': '300x600', 'foobar': '300x600' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 } ] } @@ -1110,7 +1146,7 @@ export function getBidResponsesFromAPI() { export function getAdServerTargeting() { return { '/19968336/header-bid-tag-0': { - 'foobar': '300x250', + 'foobar': '0x0,300x250,300x600', 'hb_size': '300x250', 'hb_pb': '10.00', 'hb_adid': '233bcbee889d46d', @@ -1179,11 +1215,7 @@ export function getTargetingKeys() { ], [ 'foobar', - '300x250' - ], - [ - 'foobar', - '300x250' + ['0x0', '300x250', '300x600'] ] ]; } @@ -1197,7 +1229,7 @@ export function getTargetingKeysBidLandscape() { 'appnexus' ], [ - 'hb_adid', + 'hb_adid_appnexus', '233bcbee889d46d' ], [ @@ -1210,11 +1242,7 @@ export function getTargetingKeysBidLandscape() { ], [ 'foobar', - '300x250' - ], - [ - 'foobar', - '300x250' + ['0x0', '300x250', '300x600'] ], [ 'hb_bidder_triplelift', @@ -1236,10 +1264,6 @@ export function getTargetingKeysBidLandscape() { 'hb_bidder_appnexus', 'appnexus' ], - [ - 'hb_adid_appnexus', - '233bcbee889d46d' - ], [ 'hb_pb_appnexus', '10.00' @@ -1334,7 +1358,7 @@ export function getTargetingKeysBidLandscape() { export function getBidRequestedPayload() { return { 'bidderCode': 'adequant', - 'requestId': '150f361b202aa8', + 'auctionId': '150f361b202aa8', 'bidderRequestId': '2b193b7a6ff421', 'bids': [ { @@ -1364,7 +1388,7 @@ export function getBidRequestedPayload() { ], 'bidId': '39032dc5c7e834', 'bidderRequestId': '2b193b7a6ff421', - 'requestId': '150f361b202aa8' + 'auctionId': '150f361b202aa8' } ], 'start': 1465426155412 @@ -1380,3 +1404,41 @@ export function getCurrencyRates() { } }; } + +export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, adUnitCode, adId, status, ttl}) { + let bid = { + 'bidderCode': bidder, + 'width': '300', + 'height': '250', + 'statusMessage': 'Bid available', + 'adId': adId, + 'cpm': cpm, + 'ad': 'markup', + 'ad_id': adId, + 'sizeId': '15', + 'requestTimestamp': 1454535718610, + 'responseTimestamp': responseTimestamp, + 'auctionId': auctionId, + 'timeToRespond': 123, + 'pbLg': '0.50', + 'pbMg': '0.50', + 'pbHg': '0.53', + 'adUnitCode': adUnitCode, + 'bidder': bidder, + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': bidder, + 'hb_adid': adId, + 'hb_pb': cpm, + 'foobar': '300x250' + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': (!ttl) ? 300 : ttl + }; + + if (typeof status !== 'undefined') { + bid.status = status; + } + return bid; +} diff --git a/test/fixtures/video/adUnit.json b/test/fixtures/video/adUnit.json index 6d2b7c385ad..df55eb25d79 100644 --- a/test/fixtures/video/adUnit.json +++ b/test/fixtures/video/adUnit.json @@ -4,7 +4,7 @@ "mediaType": "video", "bids": [ { - "bidder": "appnexusAst", + "bidder": "appnexus", "params": { "placementId": "9333431", "video": { diff --git a/test/fixtures/video/bidRequest.json b/test/fixtures/video/bidRequest.json index 75f054611c4..2a598c50183 100644 --- a/test/fixtures/video/bidRequest.json +++ b/test/fixtures/video/bidRequest.json @@ -1,10 +1,10 @@ { "auctionStart": 1462918897459, - "bidderCode": "appnexusAst", + "bidderCode": "appnexus", "bidderRequestId": "2946b569352ef2", "bids": [ { - "bidder": "appnexusAst", + "bidder": "appnexus", "params": { "placementId": "9333431", "video": { @@ -16,11 +16,11 @@ "sizes": [640,480], "bidId": "392b5a6b05d648", "bidderRequestId": "2946b569352ef2", - "requestId": "6172477f-987f-4523-a967-fa6d7a434ddf", + "auctionId": "6172477f-987f-4523-a967-fa6d7a434ddf", "startTime": 1462918897462 } ], - "requestId": "6172477f-987f-4523-a967-fa6d7a434ddf", + "auctionId": "6172477f-987f-4523-a967-fa6d7a434ddf", "start": 1462918897460, "timeout": 5000 } diff --git a/test/fixtures/video/vastPayloadResponse.json b/test/fixtures/video/vastPayloadResponse.json index 9b621c21d30..2f72907817f 100644 --- a/test/fixtures/video/vastPayloadResponse.json +++ b/test/fixtures/video/vastPayloadResponse.json @@ -1,13 +1,13 @@ { "adUnitCode": "video1", - "bidder": "appnexusAst", - "bidderCode": "appnexusAst", - "code": "appnexusAst", + "bidder": "appnexus", + "bidderCode": "appnexus", + "code": "appnexus", "dealId": "foo", "cpm": 0.1, "height": 480, "mediaType": "video", - "requestId": "6172477f-987f-4523-a967-fa6d7a434ddf", + "auctionId": "6172477f-987f-4523-a967-fa6d7a434ddf", "vastXml": "", "width": 640 } diff --git a/test/fixtures/video/vastUrlResponse.json b/test/fixtures/video/vastUrlResponse.json index cba0798251d..a842ed10a71 100644 --- a/test/fixtures/video/vastUrlResponse.json +++ b/test/fixtures/video/vastUrlResponse.json @@ -1,13 +1,13 @@ { "adUnitCode": "video1", - "bidder": "appnexusAst", - "bidderCode": "appnexusAst", - "code": "appnexusAst", + "bidder": "appnexus", + "bidderCode": "appnexus", + "code": "appnexus", "dealId": "foo", "cpm": 0.1, "height": 480, "mediaType": "video", - "requestId": "6172477f-987f-4523-a967-fa6d7a434ddf", + "auctionId": "6172477f-987f-4523-a967-fa6d7a434ddf", "vastUrl": "www.myVastUrl.com", "width": 640 } diff --git a/test/helpers/index_adapter_utils.js b/test/helpers/index_adapter_utils.js index 716ec1ff4f3..f01145b573d 100644 --- a/test/helpers/index_adapter_utils.js +++ b/test/helpers/index_adapter_utils.js @@ -203,7 +203,7 @@ exports.matchBidsOnSID = function(lhs, rhs) { var compared = compareOnKeys(lstore, rstore); var matched = compared.intersection.map(function(pair) { return { configured: pair.left, sent: pair.right, name: pair.name } }); - return { unmatched: { configured: compared.lhsOnly, sent: compared.rhsOnly }, matched: matched}; + return { unmatched: { configured: compared.lhsOnly, sent: compared.rhsOnly }, matched: matched }; } exports.matchBidsOnSize = function(lhs, rhs) { @@ -220,12 +220,12 @@ exports.matchBidsOnSize = function(lhs, rhs) { } var lstore = createObjectFromArray(configured); - var rstore = createObjectFromArray(rhs.map(bid => [ bid.banner.w + 'x' + bid.banner.h, bid])); + var rstore = createObjectFromArray(rhs.map(bid => [ bid.banner.w + 'x' + bid.banner.h, bid ])); var compared = compareOnKeys(lstore, rstore); var matched = compared.intersection.map(function(pair) { return { configured: pair.left, sent: pair.right, name: pair.name } }); - return { unmatched: { configured: compared.lhsOnly, sent: compared.rhsOnly }, matched: matched}; + return { unmatched: { configured: compared.lhsOnly, sent: compared.rhsOnly }, matched: matched }; } exports.getBidResponse = function(configuredBids, urlJSON, optionalPriceLevel, optionalResponseIdentifier, optionalPassOnBid, optionalResponseParam) { diff --git a/test/pages/video.html b/test/pages/video.html index 8d28650cbfc..c6a72b6e26b 100644 --- a/test/pages/video.html +++ b/test/pages/video.html @@ -36,7 +36,7 @@ }, bids: [ { - bidder: 'appnexusAst', + bidder: 'appnexus', params: { placementId: '9333431', video: { diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index 3eeb5a9efee..d6b227e9aac 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -6,6 +6,8 @@ const BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; const BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; const BID_WON = CONSTANTS.EVENTS.BID_WON; const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; +const AD_RENDER_FAILED = CONSTANTS.EVENTS.AD_RENDER_FAILED; + const AnalyticsAdapter = require('src/AnalyticsAdapter').default; const config = { url: 'http://localhost:9999/src/adapters/analytics/libraries/example.js', @@ -21,152 +23,163 @@ FEATURE: Analytics Adapters API SCENARIO: A publisher enables analytics GIVEN a global object \`window['testGlobal']\` AND an \`example\` instance of \`AnalyticsAdapter\`\n`, () => { - describe(`WHEN an event occurs that is to be tracked\n`, () => { - const eventType = BID_REQUESTED; - const args = { some: 'data' }; - const adapter = new AnalyticsAdapter(config); + describe(`WHEN an event occurs that is to be tracked\n`, () => { + const eventType = BID_REQUESTED; + const args = { some: 'data' }; + const adapter = new AnalyticsAdapter(config); + var spyTestGlobal = sinon.spy(window, config.global); + + adapter.track({ eventType, args }); + + it(`THEN should call \`window.${config.global}\` function\n`, () => { + assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); + assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); + }); + window[config.global].restore(); + }); + + describe(`WHEN an event occurs before tracking library is available\n`, () => { + const eventType = BID_RESPONSE; + const args = { wat: 'wot' }; + const adapter = new AnalyticsAdapter(config); + + window[config.global] = null; + events.emit(BID_RESPONSE, args); + + describe(`AND the adapter is then enabled\n`, () => { + window[config.global] = () => {}; + var spyTestGlobal = sinon.spy(window, config.global); - adapter.track({ eventType, args }); + adapter.enableAnalytics(); - it(`THEN should call \`window.${config.global}\` function\n`, () => { + it(`THEN should queue the event first and then track it\n`, () => { assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); }); + + adapter.disableAnalytics(); window[config.global].restore(); }); + }); - describe(`WHEN an event occurs before tracking library is available\n`, () => { - const eventType = BID_RESPONSE; - const args = { wat: 'wot' }; - const adapter = new AnalyticsAdapter(config); - - window[config.global] = null; - events.emit(BID_RESPONSE, args); - - describe(`AND the adapter is then enabled\n`, () => { - window[config.global] = () => {}; + describe(`WHEN an event occurs after enable analytics\n`, () => { + var spyTestGlobal, + adapter; - var spyTestGlobal = sinon.spy(window, config.global); + beforeEach(() => { + adapter = new AnalyticsAdapter(config); + spyTestGlobal = sinon.spy(window, config.global); - adapter.enableAnalytics(); + sinon.stub(events, 'getEvents').returns([]); // these tests shouldn't be affected by previous tests + }); - it(`THEN should queue the event first and then track it\n`, () => { - assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); - assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); - }); + afterEach(() => { + adapter.disableAnalytics(); + window[config.global].restore(); - adapter.disableAnalytics(); - window[config.global].restore(); - }); + events.getEvents.restore(); }); - describe(`WHEN an event occurs after enable analytics\n`, () => { - var spyTestGlobal, - adapter; + it('SHOULD call global when a bidWon event occurs', () => { + const eventType = BID_WON; + const args = { more: 'info' }; - beforeEach(() => { - adapter = new AnalyticsAdapter(config); - spyTestGlobal = sinon.spy(window, config.global); + adapter.enableAnalytics(); + events.emit(eventType, args); - sinon.stub(events, 'getEvents', () => []); // these tests shouldn't be affected by previous tests - }); + assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); + assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); + }); - afterEach(() => { - adapter.disableAnalytics(); - window[config.global].restore(); + it('SHOULD call global when a adRenderFailed event occurs', () => { + const eventType = AD_RENDER_FAILED; + const args = { call: 'adRenderFailed' }; - events.getEvents.restore(); - }); + adapter.enableAnalytics(); + events.emit(eventType, args); - it('SHOULD call global when a bidWon event occurs', () => { - const eventType = BID_WON; - const args = { more: 'info' }; + assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); + assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); + }); - adapter.enableAnalytics(); - events.emit(eventType, args); + it('SHOULD call global when a bidRequest event occurs', () => { + const eventType = BID_REQUESTED; + const args = { call: 'request' }; - assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); - assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); - }); + adapter.enableAnalytics(); + events.emit(eventType, args); - it('SHOULD call global when a bidRequest event occurs', () => { - const eventType = BID_REQUESTED; - const args = { call: 'request' }; + assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); + assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); + }); - adapter.enableAnalytics(); - events.emit(eventType, args); + it('SHOULD call global when a bidResponse event occurs', () => { + const eventType = BID_RESPONSE; + const args = { call: 'response' }; - assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); - assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); - }); + adapter.enableAnalytics(); + events.emit(eventType, args); - it('SHOULD call global when a bidResponse event occurs', () => { - const eventType = BID_RESPONSE; - const args = { call: 'response' }; + assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); + assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); + }); - adapter.enableAnalytics(); - events.emit(eventType, args); + it('SHOULD call global when a bidTimeout event occurs', () => { + const eventType = BID_TIMEOUT; + const args = { call: 'timeout' }; - assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); - assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); - }); + adapter.enableAnalytics(); + events.emit(eventType, args); - it('SHOULD call global when a bidTimeout event occurs', () => { - const eventType = BID_TIMEOUT; - const args = { call: 'timeout' }; + assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); + assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); + }); - adapter.enableAnalytics(); - events.emit(eventType, args); + it('SHOULD NOT call global again when adapter.enableAnalytics is called with previous timeout', () => { + const eventType = BID_TIMEOUT; + const args = { call: 'timeout' }; - assert.ok(spyTestGlobal.args[0][1] === eventType, `with expected event type\n`); - assert.deepEqual(spyTestGlobal.args[0][2], args, `with expected event data\n`); - }); + events.emit(eventType, args); + adapter.enableAnalytics(); + events.emit(eventType, args); - it('SHOULD NOT call global again when adapter.enableAnalytics is called with previous timeout', () => { - const eventType = BID_TIMEOUT; - const args = { call: 'timeout' }; + assert(spyTestGlobal.calledOnce === true); + }); - events.emit(eventType, args); - adapter.enableAnalytics(); - events.emit(eventType, args); + describe(`AND sampling is enabled\n`, () => { + const eventType = BID_WON; + const args = { more: 'info' }; - assert(spyTestGlobal.calledOnce === true); + beforeEach(() => { + sinon.stub(Math, 'random').returns(0.5); }); - describe(`AND sampling is enabled\n`, () => { - const eventType = BID_WON; - const args = { more: 'info' }; - - beforeEach(() => { - sinon.stub(Math, 'random', () => 0.5); - }); + afterEach(() => { + Math.random.restore(); + }); - afterEach(() => { - Math.random.restore(); + it(`THEN should enable analytics when random number is in sample range`, () => { + adapter.enableAnalytics({ + options: { + sampling: 0.75 + } }); + events.emit(eventType, args); - it(`THEN should enable analytics when random number is in sample range`, () => { - adapter.enableAnalytics({ - options: { - sampling: 0.75 - } - }); - events.emit(eventType, args); + assert(spyTestGlobal.called === true); + }); - assert(spyTestGlobal.called === true); + it(`THEN should disable analytics when random number is outside sample range`, () => { + adapter.enableAnalytics({ + options: { + sampling: 0.25 + } }); + events.emit(eventType, args); - it(`THEN should disable analytics when random number is outside sample range`, () => { - adapter.enableAnalytics({ - options: { - sampling: 0.25 - } - }); - events.emit(eventType, args); - - assert(spyTestGlobal.called === false); - }); + assert(spyTestGlobal.called === false); }); }); }); +}); diff --git a/test/spec/adUnits_spec.js b/test/spec/adUnits_spec.js index 01a4c4cd441..f15ba41eb23 100644 --- a/test/spec/adUnits_spec.js +++ b/test/spec/adUnits_spec.js @@ -82,12 +82,12 @@ describe('Publisher API _ AdUnits', function () { assert.strictEqual(bids2[1].params.placementId, '827326', 'adUnit2 bids2 params.placementId'); }); - it('both add unit should contains a transactionid.'), function() { - assert.exist(adUnit1.transationId) - assert.exist(adUnit2.transationId) + it('both add unit should contains a transactionId', function() { + assert.isString(adUnit1.transactionId); + assert.isString(adUnit2.transactionId); - assert.strictEqual(false, adUnit1.transationId === adUnit2.transationId) - } + assert.strictEqual(false, adUnit1.transactionId === adUnit2.transactionId); + }); it('the second adUnits value should be same with the adUnits that is added by $$PREBID_GLOBAL$$.addAdUnits();', function () { assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); diff --git a/test/spec/adloader_spec.js b/test/spec/adloader_spec.js index 951631d7eac..55224cb0aab 100644 --- a/test/spec/adloader_spec.js +++ b/test/spec/adloader_spec.js @@ -1,4 +1,31 @@ +import * as utils from 'src/utils'; +import * as adLoader from 'src/adloader'; + describe('adLoader', function () { - var assert = require('chai').assert, - adLoader = require('../../src/adloader'); + let utilsinsertElementStub; + let utilsLogErrorStub; + + beforeEach(() => { + utilsinsertElementStub = sinon.stub(utils, 'insertElement'); + utilsLogErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(() => { + utilsinsertElementStub.restore(); + utilsLogErrorStub.restore(); + }); + + describe('loadExternalScript', () => { + it('requires moduleCode to be included on the request', () => { + adLoader.loadExternalScript('someURL'); + expect(utilsLogErrorStub.called).to.be.true; + expect(utilsinsertElementStub.called).to.be.false; + }); + + it('only allows whitelisted vendors to load scripts', () => { + adLoader.loadExternalScript('someURL', 'criteo'); + expect(utilsLogErrorStub.called).to.be.false; + expect(utilsinsertElementStub.called).to.be.true; + }); + }); }); diff --git a/test/spec/api_spec.js b/test/spec/api_spec.js index b3e2e0fc666..41fafb080ad 100755 --- a/test/spec/api_spec.js +++ b/test/spec/api_spec.js @@ -47,10 +47,6 @@ describe('Publisher API', function () { assert.isFunction($$PREBID_GLOBAL$$.setTargetingForGPTAsync); }); - it('should have function $$PREBID_GLOBAL$$.allBidsAvailable', function () { - assert.isFunction($$PREBID_GLOBAL$$.allBidsAvailable); - }); - it('should have function $$PREBID_GLOBAL$$.renderAd', function () { assert.isFunction($$PREBID_GLOBAL$$.renderAd); }); @@ -67,14 +63,6 @@ describe('Publisher API', function () { assert.isFunction($$PREBID_GLOBAL$$.addAdUnits); }); - it('should have function $$PREBID_GLOBAL$$.addCallback', function () { - assert.isFunction($$PREBID_GLOBAL$$.addCallback); - }); - - it('should have function $$PREBID_GLOBAL$$.removeCallback', function () { - assert.isFunction($$PREBID_GLOBAL$$.removeCallback); - }); - it('should have function $$PREBID_GLOBAL$$.aliasBidder', function () { assert.isFunction($$PREBID_GLOBAL$$.aliasBidder); }); diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js new file mode 100644 index 00000000000..ef50d2b6294 --- /dev/null +++ b/test/spec/auctionmanager_spec.js @@ -0,0 +1,830 @@ +import { auctionManager, newAuctionManager } from 'src/auctionManager'; +import { getKeyValueTargetingPairs } from 'src/auction'; +import CONSTANTS from 'src/constants.json'; +import { adjustBids } from 'src/auction'; +import * as auctionModule from 'src/auction'; +import { newBidder, registerBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; +import * as store from 'src/videoCache'; +import * as ajaxLib from 'src/ajax'; + +const adloader = require('../../src/adloader'); +var assert = require('assert'); + +/* use this method to test individual files instead of the whole prebid.js project */ + +// TODO refactor to use the spec files +var utils = require('../../src/utils'); +var bidfactory = require('../../src/bidfactory'); +var fixtures = require('../fixtures/fixtures'); +var adaptermanager = require('src/adaptermanager'); +var events = require('src/events'); + +function timestamp() { + return new Date().getTime(); +} + +const BIDDER_CODE = 'sampleBidder'; +const BIDDER_CODE1 = 'sampleBidder1'; + +const ADUNIT_CODE = 'adUnit-code'; +const ADUNIT_CODE1 = 'adUnit-code-1'; + +function mockBid(opts) { + let bidderCode = opts && opts.bidderCode; + + return { + 'ad': 'creative', + 'cpm': '1.99', + 'width': 300, + 'height': 250, + 'bidderCode': bidderCode || BIDDER_CODE, + 'requestId': utils.getUniqueIdentifierStr(), + 'creativeId': 'id', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360 + }; +} + +function mockBidRequest(bid, opts) { + if (!bid) { + throw new Error('bid required'); + } + let bidderCode = opts && opts.bidderCode; + let adUnitCode = opts && opts.adUnitCode; + + let requestId = utils.getUniqueIdentifierStr(); + + return { + 'bidderCode': bidderCode || bid.bidderCode, + 'auctionId': '20882439e3238c', + 'bidderRequestId': requestId, + 'bids': [ + { + 'bidder': bidderCode || bid.bidderCode, + 'params': { + 'placementId': 'id' + }, + 'adUnitCode': adUnitCode || ADUNIT_CODE, + 'sizes': [[300, 250], [300, 600]], + 'bidId': bid.requestId, + 'bidderRequestId': requestId, + 'auctionId': '20882439e3238c' + } + ], + 'auctionStart': 1505250713622, + 'timeout': 3000 + }; +} + +function mockBidder(bidderCode, bids) { + let spec = { + code: bidderCode, + isBidRequestValid: sinon.stub(), + buildRequests: sinon.stub(), + interpretResponse: sinon.stub(), + getUserSyncs: sinon.stub() + }; + + spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); + spec.isBidRequestValid.returns(true); + spec.interpretResponse.returns(bids); + + return spec; +} + +const TEST_BIDS = [mockBid()]; +const TEST_BID_REQS = TEST_BIDS.map(mockBidRequest); + +function mockAjaxBuilder() { + return function(url, callback) { + const fakeResponse = sinon.stub(); + fakeResponse.returns('headerContent'); + callback.success('response body', { getResponseHeader: fakeResponse }); + }; +} + +describe('auctionmanager.js', function () { + let xhr; + + before(() => { + xhr = sinon.useFakeXMLHttpRequest(); + }); + + after(() => { + xhr.restore(); + }); + + describe('getKeyValueTargetingPairs', function () { + const DEFAULT_BID = { + cpm: 5.578, + pbLg: 5.50, + pbMg: 5.50, + pbHg: 5.57, + pbAg: 5.50, + + height: 300, + width: 250, + getSize() { + return this.height + 'x' + this.width; + }, + + adUnitCode: '12345', + bidderCode: 'appnexus', + adId: '1adId', + source: 'client', + mediaType: 'banner', + }; + + /* return the expected response for a given bid, filter by keys if given */ + function getDefaultExpected(bid, keys) { + var expected = { + 'hb_bidder': bid.bidderCode, + 'hb_adid': bid.adId, + 'hb_pb': bid.pbMg, + 'hb_size': bid.getSize(), + 'hb_source': bid.source, + 'hb_format': bid.mediaType, + }; + + if (!keys) { + return expected; + } + + return keys.reduce((map, key) => { + map[key] = expected[key]; + return map; + }, {}); + } + + var bid = {}; + + before(function () { + bid = Object.assign({}, DEFAULT_BID); + }); + + it('No bidder level configuration defined - default', function () { + var expected = getDefaultExpected(bid); + var response = getKeyValueTargetingPairs(bid.bidderCode, bid, CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); + assert.deepEqual(response, expected); + }); + + it('Custom configuration for all bidders', function () { + $$PREBID_GLOBAL$$.bidderSettings = + { + standard: { + adserverTargeting: [ + { + key: 'hb_bidder', + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: 'hb_adid', + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: 'hb_pb', + val: function (bidResponse) { + // change default here + return bidResponse.pbHg; + } + }, { + key: 'hb_size', + val: function (bidResponse) { + return bidResponse.size; + } + }, + { + key: 'hb_source', + val: function (bidResponse) { + return bidResponse.source; + } + }, + { + key: 'hb_format', + val: function (bidResponse) { + return bidResponse.mediaType; + } + }, + ] + + } + }; + + var expected = getDefaultExpected(bid); + expected.hb_pb = bid.pbHg; + + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); + assert.deepEqual(response, expected); + }); + + it('Custom configuration for one bidder', function () { + $$PREBID_GLOBAL$$.bidderSettings = + { + appnexus: { + adserverTargeting: [ + { + key: 'hb_bidder', + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: 'hb_adid', + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: 'hb_pb', + val: function (bidResponse) { + // change default here + return bidResponse.pbHg; + } + }, { + key: 'hb_size', + val: function (bidResponse) { + return bidResponse.size; + } + } + ] + + } + }; + + var expected = getDefaultExpected(bid); + expected.hb_pb = bid.pbHg; + + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); + assert.deepEqual(response, expected); + }); + + it('Custom configuration for one bidder - not matched', function () { + $$PREBID_GLOBAL$$.bidderSettings = + { + nonExistentBidder: { + adserverTargeting: [ + { + key: 'hb_bidder', + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: 'hb_adid', + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: 'hb_pb', + val: function (bidResponse) { + // change default here + return bidResponse.pbHg; + } + }, { + key: 'hb_size', + val: function (bidResponse) { + return bidResponse.size; + } + } + ] + + } + }; + var expected = getDefaultExpected(bid); + + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); + assert.deepEqual(response, expected); + }); + + it('Custom bidCpmAdjustment for one bidder and inherit standard but doesn\'t use standard bidCpmAdjustment', function () { + $$PREBID_GLOBAL$$.bidderSettings = + { + appnexus: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.7; + }, + }, + standard: { + bidCpmAdjustment: function (bidCpm) { + return 200; + }, + adserverTargeting: [ + { + key: 'hb_bidder', + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: 'hb_adid', + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: 'hb_pb', + val: function (bidResponse) { + // change default here + return 10.00; + } + } + ] + + } + }; + var expected = getDefaultExpected(bid, ['hb_bidder', 'hb_adid']); + expected.hb_pb = 10.0; + + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); + assert.deepEqual(response, expected); + }); + + it('Standard bidCpmAdjustment changes the bid of any bidder', function () { + const bid = Object.assign({}, + bidfactory.createBid(2), + fixtures.getBidResponses()[5] + ); + + assert.equal(bid.cpm, 0.5); + + $$PREBID_GLOBAL$$.bidderSettings = + { + standard: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.5; + } + } + }; + + adjustBids(bid) + assert.equal(bid.cpm, 0.25); + }); + + it('Custom bidCpmAdjustment AND custom configuration for one bidder and inherit standard settings', function () { + $$PREBID_GLOBAL$$.bidderSettings = + { + appnexus: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.7; + }, + adserverTargeting: [ + { + key: 'hb_bidder', + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: 'hb_adid', + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: 'hb_pb', + val: function (bidResponse) { + // change default here + return 15.00; + } + } + ] + }, + standard: { + adserverTargeting: [ + { + key: 'hb_bidder', + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: 'hb_adid', + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: 'hb_pb', + val: function (bidResponse) { + // change default here + return 10.00; + }, + }, + { + key: 'hb_size', + val: function (bidResponse) { + return bidResponse.size; + } + } + ] + + } + }; + var expected = getDefaultExpected(bid, ['hb_bidder', 'hb_adid', 'hb_size']); + expected.hb_pb = 15.0; + + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); + assert.deepEqual(response, expected); + }); + + it('sendStandardTargeting=false, and inherit custom', function () { + $$PREBID_GLOBAL$$.bidderSettings = + { + appnexus: { + sendStandardTargeting: false, + adserverTargeting: [ + { + key: 'hb_bidder', + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: 'hb_adid', + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: 'hb_pb', + val: function (bidResponse) { + return bidResponse.pbHg; + } + } + ] + } + }; + var expected = getDefaultExpected(bid); + expected.hb_pb = 5.57; + + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); + assert.deepEqual(response, expected); + assert.equal(bid.sendStandardTargeting, false); + }); + + it('suppressEmptyKeys=true', function() { + $$PREBID_GLOBAL$$.bidderSettings = + { + standard: { + suppressEmptyKeys: true, + adserverTargeting: [ + { + key: 'aKeyWithAValue', + val: 42 + }, + { + key: 'aKeyWithAnEmptyValue', + val: '' + } + ] + } + }; + + var expected = { + 'aKeyWithAValue': 42 + }; + + var response = getKeyValueTargetingPairs(bid.bidderCode, bid); + assert.deepEqual(response, expected); + }); + }); + + describe('adjustBids', () => { + it('should adjust bids if greater than zero and pass copy of bid object', () => { + const bid = Object.assign({}, + bidfactory.createBid(2), + fixtures.getBidResponses()[5] + ); + + assert.equal(bid.cpm, 0.5); + + $$PREBID_GLOBAL$$.bidderSettings = + { + brealtime: { + bidCpmAdjustment: function (bidCpm, bidObj) { + assert.deepEqual(bidObj, bid); + if (bidObj.adUnitCode === 'negative') { + return bidCpm * -0.5; + } + if (bidObj.adUnitCode === 'zero') { + return 0; + } + return bidCpm * 0.5; + }, + }, + standard: { + adserverTargeting: [ + ] + } + }; + + // negative + bid.adUnitCode = 'negative'; + adjustBids(bid) + assert.equal(bid.cpm, 0.5); + + // positive + bid.adUnitCode = 'normal'; + adjustBids(bid) + assert.equal(bid.cpm, 0.25); + + // zero + bid.adUnitCode = 'zero'; + adjustBids(bid) + assert.equal(bid.cpm, 0); + + // reset bidderSettings so we don't mess up further tests + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + }); + + describe('addBidResponse', () => { + let createAuctionStub; + let adUnits; + let adUnitCodes; + let spec; + let auction; + let ajaxStub; + let bids = TEST_BIDS; + let makeRequestsStub; + + before(() => { + makeRequestsStub = sinon.stub(adaptermanager, 'makeBidRequests'); + + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); + }); + + after(() => { + ajaxStub.restore(); + adaptermanager.makeBidRequests.restore(); + }); + + describe('when auction timeout is 3000', () => { + let loadScriptStub; + before(() => { + makeRequestsStub.returns(TEST_BID_REQS); + }); + beforeEach(() => { + adUnits = [{ + code: ADUNIT_CODE, + bids: [ + {bidder: BIDDER_CODE, params: {placementId: 'id'}}, + ] + }]; + adUnitCodes = [ADUNIT_CODE]; + auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 3000}); + createAuctionStub = sinon.stub(auctionModule, 'newAuction'); + createAuctionStub.returns(auction); + + loadScriptStub = sinon.stub(adloader, 'loadScript').callsFake((...args) => { + args[1](); + }); + + spec = mockBidder(BIDDER_CODE, bids); + registerBidder(spec); + }); + + afterEach(() => { + auctionModule.newAuction.restore(); + loadScriptStub.restore(); + }); + + function checkPbDg(cpm, expected, msg) { + return function() { + bids[0].cpm = cpm; + auction.callBids(); + + let registeredBid = auction.getBidsReceived().pop(); + assert.equal(registeredBid.pbDg, expected, msg); + }; + }; + + it('should return proper price bucket increments for dense mode when cpm is in range 0-3', + checkPbDg('1.99', '1.99', '0 - 3 hits at to 1 cent increment')); + + it('should return proper price bucket increments for dense mode when cpm is in range 3-8', + checkPbDg('4.39', '4.35', '3 - 8 hits at 5 cent increment')); + + it('should return proper price bucket increments for dense mode when cpm is in range 8-20', + checkPbDg('19.99', '19.50', '8 - 20 hits at 50 cent increment')); + + it('should return proper price bucket increments for dense mode when cpm is 20+', + checkPbDg('73.07', '20.00', '20+ caps at 20.00')); + + it('should place dealIds in adserver targeting', () => { + bids[0].dealId = 'test deal'; + auction.callBids(); + + let registeredBid = auction.getBidsReceived().pop(); + assert.equal(registeredBid.adserverTargeting[`hb_deal`], 'test deal', 'dealId placed in adserverTargeting'); + }); + + it('should pass through default adserverTargeting sent from adapter', () => { + bids[0].adserverTargeting = {}; + bids[0].adserverTargeting.extra = 'stuff'; + auction.callBids(); + + let registeredBid = auction.getBidsReceived().pop(); + assert.equal(registeredBid.adserverTargeting.hb_bidder, BIDDER_CODE); + assert.equal(registeredBid.adserverTargeting.extra, 'stuff'); + }); + + it('installs publisher-defined renderers on bids', () => { + let renderer = { + url: 'renderer.js', + render: (bid) => bid + }; + let bidRequests = [Object.assign({}, TEST_BID_REQS[0])]; + bidRequests[0].bids[0] = Object.assign({ renderer }, bidRequests[0].bids[0]); + makeRequestsStub.returns(bidRequests); + + let bids1 = Object.assign({}, + bids[0], + { + bidderCode: BIDDER_CODE, + mediaType: 'video-outstream', + } + ); + spec.interpretResponse.returns(bids1); + auction.callBids(); + const addedBid = auction.getBidsReceived().pop(); + assert.equal(addedBid.renderer.url, 'renderer.js'); + }); + }); + + describe('when auction timeout is 20', () => { + let loadScriptStub; + let eventsEmitSpy; + let getBidderRequestStub; + + before(() => { + bids = [mockBid(), mockBid({ bidderCode: BIDDER_CODE1 })]; + let bidRequests = bids.map(bid => mockBidRequest(bid)); + + makeRequestsStub.returns(bidRequests); + }); + + beforeEach(() => { + adUnits = [{ + code: ADUNIT_CODE, + bids: [ + {bidder: BIDDER_CODE, params: {placementId: 'id'}}, + ] + }]; + adUnitCodes = [ADUNIT_CODE]; + + auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 20}); + createAuctionStub = sinon.stub(auctionModule, 'newAuction'); + createAuctionStub.returns(auction); + + loadScriptStub = sinon.stub(adloader, 'loadScript').callsFake((...args) => { + args[1](); + }); + + spec = mockBidder(BIDDER_CODE, [bids[0]]); + registerBidder(spec); + + // Timeout is checked when bid is received. If that bid is the only one + // auction is waiting for, timeout is not emitted, so we need to add a + // second bidder to get timeout event. + let spec1 = mockBidder(BIDDER_CODE1, [bids[1]]); + registerBidder(spec1); + + eventsEmitSpy = sinon.spy(events, 'emit'); + + let origGBR = utils.getBidderRequest; + getBidderRequestStub = sinon.stub(utils, 'getBidderRequest'); + getBidderRequestStub.callsFake((bidRequests, bidder, adUnitCode) => { + let req = origGBR(bidRequests, bidder, adUnitCode); + req.start = 1000; + return req; + }); + }); + afterEach(() => { + auctionModule.newAuction.restore(); + loadScriptStub.restore(); + events.emit.restore(); + getBidderRequestStub.restore(); + }); + it('should emit BID_TIMEOUT for timed out bids', () => { + auction.callBids(); + assert.ok(eventsEmitSpy.calledWith(CONSTANTS.EVENTS.BID_TIMEOUT), 'emitted events BID_TIMEOUT'); + }); + }); + }); + + describe('addBidResponse', () => { + let createAuctionStub; + let adUnits; + let adUnitCodes; + let spec; + let spec1; + let auction; + let ajaxStub; + + let bids = TEST_BIDS; + let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; + + before(() => { + let bidRequests = [ + mockBidRequest(bids[0]), + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }) + ]; + let makeRequestsStub = sinon.stub(adaptermanager, 'makeBidRequests'); + makeRequestsStub.returns(bidRequests); + + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); + }); + + after(() => { + ajaxStub.restore(); + adaptermanager.makeBidRequests.restore(); + }); + + beforeEach(() => { + adUnits = [{ + code: ADUNIT_CODE, + bids: [ + {bidder: BIDDER_CODE, params: {placementId: 'id'}}, + ] + }, { + code: ADUNIT_CODE1, + bids: [ + {bidder: BIDDER_CODE1, params: {placementId: 'id'}}, + ] + }]; + adUnitCodes = adUnits.map(({ code }) => code); + auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 3000}); + createAuctionStub = sinon.stub(auctionModule, 'newAuction'); + createAuctionStub.returns(auction); + + spec = mockBidder(BIDDER_CODE, bids); + spec1 = mockBidder(BIDDER_CODE1, bids1); + + registerBidder(spec); + registerBidder(spec1); + }); + + afterEach(() => { + auctionModule.newAuction.restore(); + }); + + it('should not alter bid adID', () => { + auction.callBids(); + + const addedBid2 = auction.getBidsReceived().pop(); + assert.equal(addedBid2.adId, bids1[0].requestId); + const addedBid1 = auction.getBidsReceived().pop(); + assert.equal(addedBid1.adId, bids[0].requestId); + }); + + it('should not add banner bids that have no width or height', () => { + bids1[0].width = undefined; + bids1[0].height = undefined; + + auction.callBids(); + + let length = auction.getBidsReceived().length; + const addedBid2 = auction.getBidsReceived().pop(); + assert.notEqual(addedBid2.adId, bids1[0].requestId); + assert.equal(length, 1); + }); + + it('should run auction after video bids have been cached', () => { + sinon.stub(store, 'store').callsArgWith(1, null, [{ uuid: 123 }]); + sinon.stub(config, 'getConfig').withArgs('cache.url').returns('cache-url'); + + const bidsCopy = [Object.assign({}, bids[0], { mediaType: 'video' })]; + const bids1Copy = [Object.assign({}, bids1[0], { mediaType: 'video' })]; + + spec.interpretResponse.returns(bidsCopy); + spec1.interpretResponse.returns(bids1Copy); + + auction.callBids(); + + assert.equal(auction.getBidsReceived().length, 2); + assert.equal(auction.getAuctionStatus(), 'completed'); + + config.getConfig.restore(); + store.store.restore(); + }); + + it('runs auction after video responses with multiple bid objects have been cached', () => { + sinon.stub(store, 'store').callsArgWith(1, null, [{ uuid: 123 }]); + sinon.stub(config, 'getConfig').withArgs('cache.url').returns('cache-url'); + + const bidsCopy = [ + Object.assign({}, bids[0], { mediaType: 'video' }), + Object.assign({}, bids[0], { mediaType: 'banner' }), + ]; + const bids1Copy = [ + Object.assign({}, bids1[0], { mediaType: 'video' }), + Object.assign({}, bids1[0], { mediaType: 'video' }), + ]; + + spec.interpretResponse.returns(bidsCopy); + spec1.interpretResponse.returns(bids1Copy); + + auction.callBids(); + + assert.equal(auction.getBidsReceived().length, 4); + assert.equal(auction.getAuctionStatus(), 'completed'); + + config.getConfig.restore(); + store.store.restore(); + }); + }); +}); diff --git a/test/spec/bidmanager_spec.js b/test/spec/bidmanager_spec.js deleted file mode 100644 index 967258a7fbc..00000000000 --- a/test/spec/bidmanager_spec.js +++ /dev/null @@ -1,684 +0,0 @@ -var assert = require('assert'); - -/* use this method to test individual files instead of the whole prebid.js project */ - -// TODO refactor to use the spec files -var utils = require('../../src/utils'); -var bidmanager = require('../../src/bidmanager'); -var bidfactory = require('../../src/bidfactory'); -var fixtures = require('../fixtures/fixtures'); - -function timestamp() { - return new Date().getTime(); -} - -describe('replaceTokenInString', function () { - it('should replace all given tokens in a String', function () { - var tokensToReplace = { - 'foo': 'bar', - 'zap': 'quux' - }; - - var output = utils.replaceTokenInString('hello %FOO%, I am %ZAP%', tokensToReplace, '%'); - assert.equal(output, 'hello bar, I am quux'); - }); - - it('should ignore tokens it does not see', function () { - var output = utils.replaceTokenInString('hello %FOO%', {}, '%'); - - assert.equal(output, 'hello %FOO%'); - }); -}); - -describe('bidmanager.js', function () { - describe('getKeyValueTargetingPairs', function () { - var bid = {}; - var bidPriceCpm = 5.578; - var bidPbLg = 5.50; - var bidPbMg = 5.50; - var bidPbHg = 5.57; - var bidPbAg = 5.50; - - var adUnitCode = '12345'; - var bidderCode = 'appnexus'; - var size = '300x250'; - var adId = '1adId'; - - before(function () { - bid.cpm = bidPriceCpm; - bid.pbLg = bidPbLg; - bid.pbMg = bidPbMg; - bid.pbHg = bidPbHg; - bid.pbAg = bidPbAg; - - bid.height = 300; - bid.width = 250; - bid.adUnitCode = adUnitCode; - bid.getSize = function () { - return this.height + 'x' + this.width; - }; - bid.bidderCode = bidderCode; - bid.adId = adId; - }); - - it('No bidder level configuration defined - default', function () { - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': bidPbMg, - 'hb_size': size - }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - }); - - it('Custom configuration for all bidders', function () { - $$PREBID_GLOBAL$$.bidderSettings = - { - standard: { - adserverTargeting: [ - { - key: 'hb_bidder', - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: 'hb_adid', - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: 'hb_pb', - val: function (bidResponse) { - // change default here - return bidResponse.pbHg; - } - }, { - key: 'hb_size', - val: function (bidResponse) { - return bidResponse.size; - } - } - ] - - } - }; - - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': bidPbHg, - 'hb_size': size - }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - }); - - it('Custom configuration for one bidder', function () { - $$PREBID_GLOBAL$$.bidderSettings = - { - appnexus: { - adserverTargeting: [ - { - key: 'hb_bidder', - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: 'hb_adid', - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: 'hb_pb', - val: function (bidResponse) { - // change default here - return bidResponse.pbHg; - } - }, { - key: 'hb_size', - val: function (bidResponse) { - return bidResponse.size; - } - } - ] - - } - }; - - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': bidPbHg, - 'hb_size': size - }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - }); - - it('Custom configuration for one bidder - not matched', function () { - $$PREBID_GLOBAL$$.bidderSettings = - { - nonExistentBidder: { - adserverTargeting: [ - { - key: 'hb_bidder', - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: 'hb_adid', - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: 'hb_pb', - val: function (bidResponse) { - // change default here - return bidResponse.pbHg; - } - }, { - key: 'hb_size', - val: function (bidResponse) { - return bidResponse.size; - } - } - ] - - } - }; - - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': bidPbMg, - 'hb_size': size - }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - }); - - it('Custom bidCpmAdjustment for one bidder and inherit standard but doesn\'t use standard bidCpmAdjustment', function () { - $$PREBID_GLOBAL$$.bidderSettings = - { - appnexus: { - bidCpmAdjustment: function (bidCpm) { - return bidCpm * 0.7; - }, - }, - standard: { - bidCpmAdjustment: function (bidCpm) { - return 200; - }, - adserverTargeting: [ - { - key: 'hb_bidder', - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: 'hb_adid', - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: 'hb_pb', - val: function (bidResponse) { - // change default here - return 10.00; - } - } - ] - - } - }; - - var expected = { 'hb_bidder': bidderCode, 'hb_adid': adId, 'hb_pb': 10.0 }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - }); - - it('Standard bidCpmAdjustment changes the bid of any bidder', function () { - const bid = Object.assign({}, - bidfactory.createBid(2), - fixtures.getBidResponses()[5] - ); - - assert.equal(bid.cpm, 0.5); - - $$PREBID_GLOBAL$$.bidderSettings = - { - standard: { - bidCpmAdjustment: function (bidCpm) { - return bidCpm * 0.5; - } - } - }; - - bidmanager.adjustBids(bid) - assert.equal(bid.cpm, 0.25); - }); - - it('Custom bidCpmAdjustment AND custom configuration for one bidder and inherit standard settings', function () { - $$PREBID_GLOBAL$$.bidderSettings = - { - appnexus: { - bidCpmAdjustment: function (bidCpm) { - return bidCpm * 0.7; - }, - adserverTargeting: [ - { - key: 'hb_bidder', - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: 'hb_adid', - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: 'hb_pb', - val: function (bidResponse) { - // change default here - return 15.00; - } - } - ] - }, - standard: { - adserverTargeting: [ - { - key: 'hb_bidder', - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: 'hb_adid', - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: 'hb_pb', - val: function (bidResponse) { - // change default here - return 10.00; - }, - }, - { - key: 'hb_size', - val: function (bidResponse) { - return bidResponse.size; - } - } - ] - - } - }; - - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': 15.0, - 'hb_size': '300x250' - }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - }); - - it('alwaysUseBid=true, sendStandardTargeting=false, and inherit custom', function () { - $$PREBID_GLOBAL$$.bidderSettings = - { - appnexus: { - alwaysUseBid: true, - sendStandardTargeting: false, - adserverTargeting: [ - { - key: 'hb_bidder', - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: 'hb_adid', - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: 'hb_pb', - val: function (bidResponse) { - return bidResponse.pbHg; - } - } - ] - } - }; - - var expected = { - 'hb_bidder': bidderCode, - 'hb_adid': adId, - 'hb_pb': 5.57, - 'hb_size': '300x250' - }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - assert.equal(bid.alwaysUseBid, true); - assert.equal(bid.sendStandardTargeting, false); - }); - - it('suppressEmptyKeys=true', function() { - $$PREBID_GLOBAL$$.bidderSettings = - { - standard: { - suppressEmptyKeys: true, - adserverTargeting: [ - { - key: 'aKeyWithAValue', - val: 42 - }, - { - key: 'aKeyWithAnEmptyValue', - val: '' - } - ] - } - }; - - var expected = { - 'aKeyWithAValue': 42 - }; - - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - }); - }); - - describe('adjustBids', () => { - it('should adjust bids if greater than zero and pass copy of bid object', () => { - const bid = Object.assign({}, - bidfactory.createBid(2), - fixtures.getBidResponses()[5] - ); - - assert.equal(bid.cpm, 0.5); - - $$PREBID_GLOBAL$$.bidderSettings = - { - brealtime: { - bidCpmAdjustment: function (bidCpm, bidObj) { - assert.deepEqual(bidObj, bid); - if (bidObj.adUnitCode === 'negative') { - return bidCpm * -0.5; - } - if (bidObj.adUnitCode === 'zero') { - return 0; - } - return bidCpm * 0.5; - }, - }, - standard: { - adserverTargeting: [ - ] - } - }; - - // negative - bid.adUnitCode = 'negative'; - bidmanager.adjustBids(bid) - assert.equal(bid.cpm, 0.5); - - // positive - bid.adUnitCode = 'normal'; - bidmanager.adjustBids(bid) - assert.equal(bid.cpm, 0.25); - - // zero - bid.adUnitCode = 'zero'; - bidmanager.adjustBids(bid) - assert.equal(bid.cpm, 0); - - // reset bidderSettings so we don't mess up further tests - $$PREBID_GLOBAL$$.bidderSettings = {}; - }); - }); - - describe('addBidResponse', () => { - before(() => { - $$PREBID_GLOBAL$$.adUnits = fixtures.getAdUnits(); - }); - it('should return proper price bucket increments for dense mode', () => { - const bid = Object.assign({}, - bidfactory.createBid(2), - fixtures.getBidResponses()[5] - ); - - // 0 - 3 dollars - bid.cpm = '1.99'; - let expectedIncrement = '1.99'; - bidmanager.addBidResponse(bid.adUnitCode, bid); - // pop this bid because another test relies on global $$PREBID_GLOBAL$$._bidsReceived - let registeredBid = $$PREBID_GLOBAL$$._bidsReceived.pop(); - assert.equal(registeredBid.pbDg, expectedIncrement, '0 - 3 hits at to 1 cent increment'); - - // 3 - 8 dollars - bid.cpm = '4.39'; - expectedIncrement = '4.35'; - bidmanager.addBidResponse(bid.adUnitCode, bid); - registeredBid = $$PREBID_GLOBAL$$._bidsReceived.pop(); - assert.equal(registeredBid.pbDg, expectedIncrement, '3 - 8 hits at 5 cent increment'); - - // 8 - 20 dollars - bid.cpm = '19.99'; - expectedIncrement = '19.50'; - bidmanager.addBidResponse(bid.adUnitCode, bid); - registeredBid = $$PREBID_GLOBAL$$._bidsReceived.pop(); - assert.equal(registeredBid.pbDg, expectedIncrement, '8 - 20 hits at 50 cent increment'); - - // 20+ dollars - bid.cpm = '73.07'; - expectedIncrement = '20.00'; - bidmanager.addBidResponse(bid.adUnitCode, bid); - registeredBid = $$PREBID_GLOBAL$$._bidsReceived.pop(); - assert.equal(registeredBid.pbDg, expectedIncrement, '20+ caps at 20.00'); - }); - - it('should place dealIds in adserver targeting', () => { - const bid = Object.assign({}, - bidfactory.createBid(2), - fixtures.getBidResponses()[0] - ); - - bid.dealId = 'test deal'; - bidmanager.addBidResponse(bid.adUnitCode, bid); - const addedBid = $$PREBID_GLOBAL$$._bidsReceived.pop(); - assert.equal(addedBid.adserverTargeting[`hb_deal`], bid.dealId, 'dealId placed in adserverTargeting'); - }); - - it('should pass through default adserverTargeting sent from adapter', () => { - const bid = Object.assign({}, - bidfactory.createBid(2), - fixtures.getBidResponses()[0] - ); - - bid.adserverTargeting.extra = 'stuff'; - - bidmanager.addBidResponse(bid.adUnitCode, bid); - const addedBid = $$PREBID_GLOBAL$$._bidsReceived.pop(); - assert.equal(addedBid.adserverTargeting.hb_bidder, 'triplelift'); - assert.equal(addedBid.adserverTargeting.extra, 'stuff'); - }); - - it('should not alter bid adID', () => { - const bid1 = Object.assign({}, - bidfactory.createBid(2), - fixtures.getBidResponses()[1] - ); - const bid2 = Object.assign({}, - bidfactory.createBid(2), - fixtures.getBidResponses()[3] - ); - - bidmanager.addBidResponse(bid1.adUnitCode, Object.assign({}, bid1)); - bidmanager.addBidResponse(bid2.adUnitCode, Object.assign({}, bid2)); - - const addedBid2 = $$PREBID_GLOBAL$$._bidsReceived.pop(); - assert.equal(addedBid2.adId, bid2.adId); - const addedBid1 = $$PREBID_GLOBAL$$._bidsReceived.pop(); - assert.equal(addedBid1.adId, bid1.adId); - }); - - it('should not add banner bids that have no width or height', () => { - const bid = Object.assign({}, - bidfactory.createBid(1), - { - width: undefined, - height: undefined - } - ); - - bidmanager.addBidResponse('adUnitCode', bid); - - const addedBid = $$PREBID_GLOBAL$$._bidsReceived[$$PREBID_GLOBAL$$._bidsReceived.length - 1]; - - assert.notEqual(bid.adId, addedBid.adId); - }); - - it('should add banner bids that have no width or height but single adunit size', () => { - sinon.stub(utils, 'getBidderRequest', () => ({ - start: timestamp(), - bids: [{ - sizes: [[300, 250]], - }] - })); - - const bid = Object.assign({}, - bidfactory.createBid(1), - { - width: undefined, - height: undefined - } - ); - - bidmanager.addBidResponse('adUnitCode', bid); - - const addedBid = $$PREBID_GLOBAL$$._bidsReceived[$$PREBID_GLOBAL$$._bidsReceived.length - 1]; - - assert.equal(bid.adId, addedBid.adId); - assert.equal(addedBid.width, 300); - assert.equal(addedBid.height, 250); - - utils.getBidderRequest.restore(); - }); - - it('should not add native bids that do not have required assets', () => { - sinon.stub(utils, 'getBidRequest', () => ({ - start: timestamp(), - bidder: 'appnexusAst', - mediaTypes: { - native: { - title: {required: true}, - } - }, - })); - - const bid = Object.assign({}, - bidfactory.createBid(1), - { - bidderCode: 'appnexusAst', - mediaType: 'native', - native: {title: undefined} - } - ); - - const bidsRecCount = $$PREBID_GLOBAL$$._bidsReceived.length; - bidmanager.addBidResponse('adUnit-code', bid); - assert.equal(bidsRecCount, $$PREBID_GLOBAL$$._bidsReceived.length); - - utils.getBidRequest.restore(); - }); - - it('should add native bids that do have required assets', () => { - const bidRequest = () => ({ - start: timestamp(), - bidder: 'appnexusAst', - mediaTypes: { - native: { - title: {required: true}, - } - }, - }); - sinon.stub(utils, 'getBidRequest', bidRequest); - sinon.stub(utils, 'getBidderRequest', bidRequest); - - const bid = Object.assign({}, - bidfactory.createBid(1), - { - bidderCode: 'appnexusAst', - mediaType: 'native', - native: { - title: 'foo', - clickUrl: 'example.link' - } - } - ); - - const bidsRecCount = $$PREBID_GLOBAL$$._bidsReceived.length; - bidmanager.addBidResponse('adUnit-code', bid); - assert.equal(bidsRecCount + 1, $$PREBID_GLOBAL$$._bidsReceived.length); - - utils.getBidRequest.restore(); - utils.getBidderRequest.restore(); - }); - - it('installs publisher-defined renderers on bids', () => { - sinon.stub(utils, 'getBidderRequest', () => ({ - start: timestamp(), - bids: [{ - renderer: { - url: 'renderer.js', - render: (bid) => bid - } - }] - })); - - const bid = Object.assign({}, bidfactory.createBid(1), { - bidderCode: 'appnexusAst', - mediaType: 'video-outstream', - }); - - bidmanager.addBidResponse('adUnit-code', bid); - const addedBid = $$PREBID_GLOBAL$$._bidsReceived.pop(); - assert.equal(addedBid.renderer.url, 'renderer.js'); - - utils.getBidderRequest.restore(); - }); - - it('requires a renderer on outstream bids', () => { - const bidRequest = () => ({ - start: timestamp(), - bidder: 'appnexusAst', - mediaTypes: { - video: {context: 'outstream'} - }, - }); - - sinon.stub(utils, 'getBidRequest', bidRequest); - sinon.stub(utils, 'getBidderRequest', bidRequest); - - const bid = Object.assign({}, - bidfactory.createBid(1), - { - bidderCode: 'appnexusAst', - mediaType: 'video', - renderer: {render: () => true, url: 'render.js'}, - } - ); - - const bidsRecCount = $$PREBID_GLOBAL$$._bidsReceived.length; - bidmanager.addBidResponse('adUnit-code', bid); - assert.equal(bidsRecCount + 1, $$PREBID_GLOBAL$$._bidsReceived.length); - - utils.getBidRequest.restore(); - utils.getBidderRequest.restore(); - }); - }); -}); diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 14452987091..342319b472f 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -6,18 +6,23 @@ const utils = require('src/utils'); let getConfig; let setConfig; +let setDefaults; describe('config API', () => { let logErrorSpy; + let logWarnSpy; beforeEach(() => { const config = newConfig(); getConfig = config.getConfig; setConfig = config.setConfig; + setDefaults = config.setDefaults; logErrorSpy = sinon.spy(utils, 'logError'); + logWarnSpy = sinon.spy(utils, 'logWarn'); }); afterEach(() => { utils.logError.restore(); + utils.logWarn.restore(); }); it('setConfig is a function', () => { @@ -57,41 +62,25 @@ describe('config API', () => { expect(getConfig('debug')).to.be.true; }); - // remove test when @deprecated $$PREBID_GLOBAL$$.logging removed - it('gets legacy logging in deprecation window', () => { - $$PREBID_GLOBAL$$.logging = false; - expect(getConfig('debug')).to.equal(false); - }); - it('sets bidderTimeout', () => { setConfig({ bidderTimeout: 1000 }); expect(getConfig('bidderTimeout')).to.be.equal(1000); }); - // remove test when @deprecated $$PREBID_GLOBAL$$.bidderTimeout removed - it('gets legacy bidderTimeout in deprecation window', () => { - $$PREBID_GLOBAL$$.bidderTimeout = 5000; - expect(getConfig('bidderTimeout')).to.equal(5000); - }); - it('gets user-defined publisherDomain', () => { setConfig({ publisherDomain: 'fc.kahuna' }); expect(getConfig('publisherDomain')).to.equal('fc.kahuna'); }); - // remove test when @deprecated $$PREBID_GLOBAL$$.publisherDomain removed - it('gets legacy publisherDomain in deprecation window', () => { - $$PREBID_GLOBAL$$.publisherDomain = 'ad.example.com'; - expect(getConfig('publisherDomain')).to.equal('ad.example.com'); - }); - it('gets default userSync config', () => { - expect(getConfig('userSync')).to.eql({ + const DEFAULT_USERSYNC = { syncEnabled: true, pixelEnabled: true, syncsPerBidder: 5, syncDelay: 3000 - }); + }; + setDefaults({'userSync': DEFAULT_USERSYNC}); + expect(getConfig('userSync')).to.eql(DEFAULT_USERSYNC); }); it('has subscribe functionality for adding listeners to config updates', () => { @@ -165,4 +154,15 @@ describe('config API', () => { const error = 'Prebid Error: no value passed to `setPriceGranularity()`'; assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); }); + + it('should log a warning on invalid values', () => { + setConfig({ bidderSequence: 'unrecognized sequence' }); + expect(logWarnSpy.calledOnce).to.equal(true); + }); + + it('should not log warnings when given recognized values', () => { + setConfig({ bidderSequence: 'fixed' }); + setConfig({ bidderSequence: 'random' }); + expect(logWarnSpy.called).to.equal(false); + }); }); diff --git a/test/spec/cpmBucketManager_spec.js b/test/spec/cpmBucketManager_spec.js index e5e2d03c66c..3d56299ebfd 100644 --- a/test/spec/cpmBucketManager_spec.js +++ b/test/spec/cpmBucketManager_spec.js @@ -35,6 +35,29 @@ describe('cpmBucketManager', () => { expect(JSON.stringify(output)).to.deep.equal(expected); }); + it('gets the correct custom bucket strings with irregular increment', () => { + let cpm = 14.50908; + let customConfig = { + 'buckets': [{ + 'precision': 4, + 'min': 0, + 'max': 4, + 'increment': 0.01, + }, + { + 'precision': 4, + 'min': 4, + 'max': 18, + 'increment': 0.3, + 'cap': true + } + ] + }; + let expected = '{"low":"5.00","med":"14.50","high":"14.50","auto":"14.50","dense":"14.50","custom":"14.5000"}'; + let output = getPriceBucketString(cpm, customConfig); + expect(JSON.stringify(output)).to.deep.equal(expected); + }); + it('gets the correct custom bucket strings in non-USD currency', () => { let cpm = 16.50908 * 110.49; let customConfig = { diff --git a/test/spec/e2e/gpt-examples/gpt_outstream.html b/test/spec/e2e/gpt-examples/gpt_outstream.html index 2230248886b..42ba48c98e7 100644 --- a/test/spec/e2e/gpt-examples/gpt_outstream.html +++ b/test/spec/e2e/gpt-examples/gpt_outstream.html @@ -45,7 +45,7 @@ mediaType: 'video-outstream', bids: [ { - bidder: 'appnexusAst', + bidder: 'appnexus', params: { placementId: '5768085', video: { @@ -62,7 +62,7 @@ mediaType: 'video-outstream', bids: [ { - bidder: 'appnexusAst', + bidder: 'appnexus', params: { placementId: '5768085', video: { diff --git a/test/spec/e2e/gpt-examples/gpt_yieldbot.html b/test/spec/e2e/gpt-examples/gpt_yieldbot.html new file mode 100644 index 00000000000..12766c537f6 --- /dev/null +++ b/test/spec/e2e/gpt-examples/gpt_yieldbot.html @@ -0,0 +1,241 @@ + + + + + + + + + + +
+
+ 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/hook_spec.js b/test/spec/hook_spec.js new file mode 100644 index 00000000000..1fab4ecd1b7 --- /dev/null +++ b/test/spec/hook_spec.js @@ -0,0 +1,151 @@ + +import { expect } from 'chai'; +import { createHook, hooks } from 'src/hook'; + +describe('the hook module', () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should call all sync hooks attached to a 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', () => { + 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', () => { + it('should call function as normal if no hooks attached', () => { + 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', () => { + 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', () => { + 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/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js new file mode 100644 index 00000000000..ff701d265a7 --- /dev/null +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -0,0 +1,447 @@ +const { userSync } = require('../../../src/userSync'); +const { config } = require('../../../src/config'); + +const { expect } = require('chai'); +const { + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +} = require('../../../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'; + + beforeEach(function() { + this.bidRequests = [ + { + bidId: 'b1', + bidder: '33across', + bidderRequestId: 'b1a', + params: { + siteId: SITE_ID, + productId: PRODUCT_ID + }, + adUnitCode: 'div-id', + auctionId: 'r1', + sizes: [ + [ 300, 250 ], + [ 728, 90 ] + ], + transactionId: 't1' + } + ]; + this.sandbox = sinon.sandbox.create(); + }); + + afterEach(function() { + this.sandbox.restore(); + }); + + describe('isBidRequestValid:', function () { + it('returns true when valid bid request is sent', function() { + const validBid = { + bidder: BIDDER_CODE, + params: { + siteId: SITE_ID, + productId: PRODUCT_ID + } + } + + expect(isBidRequestValid(validBid)).to.be.true; + }); + + it('returns true when valid test bid request is sent', function() { + const validBid = { + bidder: BIDDER_CODE, + params: { + siteId: SITE_ID, + productId: PRODUCT_ID, + test: 1 + } + } + + expect(isBidRequestValid(validBid)).to.be.true; + }); + + 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; + }); + + it('returns false when params not set', function() { + const invalidBid = { + bidder: 'foo' + } + + expect(isBidRequestValid(invalidBid)).to.be.false; + }); + + it('returns false when site ID is not set in params', function() { + const invalidBid = { + bidder: 'foo', + params: { + productId: PRODUCT_ID + } + } + + expect(isBidRequestValid(invalidBid)).to.be.false; + }); + + it('returns false when product ID not set in params', function() { + const invalidBid = { + bidder: 'foo', + params: { + siteId: SITE_ID + } + } + + expect(isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests:', function() { + it('returns corresponding server requests for each valid bidRequest', 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' + }; + const serverRequest = { + 'method': 'POST', + 'url': END_POINT, + 'data': JSON.stringify(ttxRequest), + 'options': { + 'contentType': 'application/json', + 'withCredentials': true + } + } + const builtServerRequests = buildRequests(this.bidRequests); + expect(builtServerRequests).to.deep.equal([ serverRequest ]); + expect(builtServerRequests.length).to.equal(1); + }); + + it('returns corresponding test server requests for each valid bidRequest', function() { + this.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' + }; + const serverRequest = { + method: 'POST', + url: 'https://foo.com/hb/', + data: JSON.stringify(ttxRequest), + options: { + contentType: 'application/json', + withCredentials: true + } + }; + + const builtServerRequests = buildRequests(this.bidRequests); + expect(builtServerRequests).to.deep.equal([ serverRequest ]); + expect(builtServerRequests.length).to.equal(1); + }); + + it('returns corresponding test server requests for each valid bidRequest', function() { + this.sandbox.stub(config, 'getConfig').callsFake(() => { + return { + 'url': 'https://foo.com/hb/' + } + }); + this.bidRequests[0].params.test = 1; + 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', + test: 1 + }; + const serverRequest = { + method: 'POST', + url: 'https://foo.com/hb/', + data: JSON.stringify(ttxRequest), + options: { + contentType: 'application/json', + withCredentials: true + } + }; + + const builtServerRequests = buildRequests(this.bidRequests); + expect(builtServerRequests).to.deep.equal([ serverRequest ]); + expect(builtServerRequests.length).to.equal(1); + }); + + afterEach(function() { + delete this.bidRequests; + }) + }); + + describe('interpretResponse', function() { + beforeEach(function() { + this.ttxRequest = { + imp: [ { + banner: { + format: [ + { + w: 300, + h: 250, + ext: {} + }, + { + w: 728, + h: 90, + ext: {} + } + ] + }, + ext: { + ttx: { + prod: PRODUCT_ID + } + } + } ], + site: { + 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: { + contentType: 'application/json', + withCredentials: false + } + }; + }); + + context('when exactly one bid is returned', function() { + it('interprets and returns the single bid response', function() { + const serverResponse = { + cur: 'USD', + ext: {}, + id: 'b1', + seatbid: [ + { + bid: [ { + id: '1', + adm: '

I am an ad

', + crid: 1, + h: 250, + w: 300, + price: 0.0938 + } ] + } + ] + }; + + const bidResponse = { + requestId: 'b1', + bidderCode: BIDDER_CODE, + cpm: 0.0938, + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 60, + creativeId: 1, + currency: 'USD', + netRevenue: true + } + + expect(interpretResponse({ body: serverResponse }, this.serverRequest)).to.deep.equal([ bidResponse ]); + }); + }); + + context('when no bids are returned', function() { + it('interprets and returns empty array', function() { + const serverResponse = { + cur: 'USD', + ext: {}, + id: 'b1', + seatbid: [] + }; + + expect(interpretResponse({ body: serverResponse }, this.serverRequest)).to.deep.equal([]); + }); + }); + + context('when more than one bids are returned', function() { + it('interprets and returns the the first bid of the first seatbid', function() { + const serverResponse = { + cur: 'USD', + ext: {}, + id: 'b1', + seatbid: [ + { + bid: [ { + id: '1', + adm: '

I am an ad

', + crid: 1, + h: 250, + w: 300, + price: 0.0940 + }, + { + id: '2', + adm: '

I am an ad

', + crid: 2, + h: 250, + w: 300, + price: 0.0938 + } + ] + }, + { + bid: [ { + id: '3', + adm: '

I am an ad

', + crid: 3, + h: 250, + w: 300, + price: 0.0938 + } ] + } + ] + }; + + const bidResponse = { + requestId: 'b1', + bidderCode: BIDDER_CODE, + cpm: 0.0940, + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 60, + creativeId: 1, + currency: 'USD', + netRevenue: true + }; + + expect(interpretResponse({ body: serverResponse }, this.serverRequest)).to.deep.equal([ bidResponse ]); + }); + }); + + context('and register user sync', function() { + it('via the production endpoint', function() { + const spy = this.sandbox.spy(userSync, 'registerSync'); + const serverResponse = { + cur: 'USD', + ext: {}, + id: 'b1', + seatbid: [] + } + interpretResponse({ body: serverResponse }, this.serverRequest); + const syncUrl = `${SYNC_ENDPOINT}&id=${this.ttxRequest.site.id}`; + + const registerSyncCalled = spy.calledWith('iframe', '33across', syncUrl); + expect(registerSyncCalled).to.be.true; + }); + + it('via the test endpoint', function() { + const spy = this.sandbox.spy(userSync, 'registerSync'); + + this.sandbox.stub(config, 'getConfig').callsFake(() => { + return { + 'syncUrl': 'https://foo.com/deb/v2?m=xch' + } + }); + + const serverResponse = { + cur: 'USD', + ext: {}, + id: 'b1', + seatbid: [] + } + interpretResponse({ body: serverResponse }, this.serverRequest); + const syncUrl = `https://foo.com/deb/v2?m=xch&id=${this.ttxRequest.site.id}`; + + const registerSyncCalled = spy.calledWith('iframe', '33across', syncUrl); + expect(registerSyncCalled).to.be.true; + }); + }); + }); +}); diff --git a/test/spec/modules/a4gBidAdapter_spec.js b/test/spec/modules/a4gBidAdapter_spec.js new file mode 100644 index 00000000000..84346a1149f --- /dev/null +++ b/test/spec/modules/a4gBidAdapter_spec.js @@ -0,0 +1,149 @@ +import { expect } from 'chai'; +import { spec } from 'modules/a4gBidAdapter'; + +describe('a4gAdapterTests', () => { + describe('bidRequestValidity', () => { + it('bidRequest with zoneId and deliveryUrl params', () => { + expect(spec.isBidRequestValid({ + bidder: 'a4g', + params: { + zoneId: 59304, + deliveryUrl: 'http://dev01.ad4game.com/v1/bid' + } + })).to.equal(true); + }); + + it('bidRequest with only zoneId', () => { + expect(spec.isBidRequestValid({ + bidder: 'a4g', + params: { + zoneId: 59304 + } + })).to.equal(true); + }); + + it('bidRequest with only deliveryUrl', () => { + expect(spec.isBidRequestValid({ + bidder: 'a4g', + params: { + deliveryUrl: 'http://dev01.ad4game.com/v1/bid' + } + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'a4g', + 'bidId': '51ef8751f9aead', + 'params': { + 'zoneId': 59304, + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }, { + 'bidder': 'a4g', + 'bidId': '51ef8751f9aead', + 'params': { + 'zoneId': 59354, + 'deliveryUrl': '//dev01.ad4game.com/v1/bid' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [[320, 50], [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.exists; + }); + + it('bidRequest zoneIds', () => { + const request = spec.buildRequests(bidRequests); + expect(request.data.zoneId).to.equal('59304;59354'); + }); + + it('bidRequest gdpr consent', () => { + const consentString = 'consentString'; + const bidderRequest = { + bidderCode: 'a4g', + 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': 'a4g', + 'bidId': '51ef8751f9aead', + 'params': { + 'zoneId': 59304, + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [[320, 50], [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/aardvarkBidAdapter_spec.js b/test/spec/modules/aardvarkBidAdapter_spec.js deleted file mode 100644 index 12a47fc946d..00000000000 --- a/test/spec/modules/aardvarkBidAdapter_spec.js +++ /dev/null @@ -1,240 +0,0 @@ -describe('aardvark adapter tests', function () { - const expect = require('chai').expect; - const Adapter = require('modules/aardvarkBidAdapter'); - const bidmanager = require('src/bidmanager'); - const adloader = require('src/adloader'); - const constants = require('src/constants.json'); - - var aardvark, - sandbox, - bidsRequestedOriginal; - - const bidderRequest = { - bidderCode: 'aardvark', - bids: [ - { - bidId: 'bidId1', - bidder: 'aardvark', - placementCode: 'foo', - sizes: [[728, 90]], - rtkid: 1, - params: { - ai: 'AH5S', - sc: 'BirH' - } - }, - { - bidId: 'bidId2', - bidder: 'aardvark', - placementCode: 'bar', - sizes: [[300, 600]], - rtkid: 1, - params: { - ai: 'AH5S', - sc: '661h' - } - } - ] - }, - - bidderRequestCustomHost = { - bidderCode: 'aardvark', - bids: [ - { - bidId: 'bidId1', - bidder: 'aardvark', - placementCode: 'foo', - sizes: [[728, 90]], - rtkid: 1, - params: { - ai: 'AH5S', - sc: 'BirH', - host: 'custom.server.com' - } - }, - { - bidId: 'bidId2', - bidder: 'aardvark', - placementCode: 'bar', - sizes: [[300, 600]], - rtkid: 1, - params: { - ai: 'AH5S', - sc: '661h', - host: 'custom.server.com' - } - } - ] - }, - - // respond - bidderResponse = [ - { - 'adm': '
', - 'cpm': 0.39440, - 'ex': '', - 'height': '90', - 'id': 'BirH', - 'nurl': '', - 'width': '728', - 'cid': 'bidId1' - }, - { - 'adm': '
', - 'cpm': 0.03485, - 'ex': '', - 'height': '600', - 'id': '661h', - 'nurl': '', - 'width': '300', - 'cid': 'bidId2' - } - ]; - - beforeEach(() => { - aardvark = new Adapter(); - sandbox = sinon.sandbox.create(); - bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; - $$PREBID_GLOBAL$$._bidsRequested = []; - }); - - afterEach(() => { - sandbox.restore(); - - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; - }); - - describe('callBids', () => { - beforeEach(() => { - sandbox.stub(adloader, 'loadScript'); - aardvark.callBids(bidderRequest); - }); - it('should load script', () => { - sinon.assert.calledOnce(adloader.loadScript); - expect(adloader.loadScript.firstCall.args[0]).to.eql( - '//thor.rtk.io/AH5S/BirH_661h/aardvark/?jsonp=$$PREBID_GLOBAL$$.aardvarkResponse&rtkreferer=localhost:9876&BirH=bidId1&661h=bidId2'); - }); - }); - - describe('callBids with custom host', () => { - beforeEach(() => { - sandbox.stub(adloader, 'loadScript'); - aardvark.callBids(bidderRequestCustomHost); - }); - it('should load script', () => { - sinon.assert.calledOnce(adloader.loadScript); - expect(adloader.loadScript.firstCall.args[0]).to.eql( - '//custom.server.com/AH5S/BirH_661h/aardvark/?jsonp=$$PREBID_GLOBAL$$.aardvarkResponse&rtkreferer=localhost:9876&BirH=bidId1&661h=bidId2'); - }); - }); - - describe('aardvarkResponse', () => { - it('should exist and be a function', () => { - expect($$PREBID_GLOBAL$$.aardvarkResponse).to.exist.and.to.be.a('function'); - }); - }); - - describe('add empty bids if no bid returned', () => { - let firstBid; - let secondBid; - - beforeEach(() => { - sandbox.stub(bidmanager, 'addBidResponse'); - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - aardvark.callBids(bidderRequest); - - $$PREBID_GLOBAL$$.aardvarkResponse([]); - - firstBid = bidmanager.addBidResponse.firstCall.args[1]; - secondBid = bidmanager.addBidResponse.secondCall.args[1]; - }); - - it('should add a bid object for each bid', () => { - sinon.assert.calledTwice(bidmanager.addBidResponse); - }); - - it('should have an error statusCode', () => { - expect(firstBid.getStatusCode()).to.eql(constants.STATUS.NO_BID); - expect(secondBid.getStatusCode()).to.eql(constants.STATUS.NO_BID); - }); - - it('should include bid request bidId as the adId', () => { - expect(firstBid).to.have.property('adId', 'bidId1'); - expect(secondBid).to.have.property('adId', 'bidId2'); - }); - - it('should pass the correct placement code as first param', () => { - let firstPlacementCode = bidmanager.addBidResponse.firstCall.args[0]; - let secondPlacementCode = bidmanager.addBidResponse.secondCall.args[0]; - - expect(firstPlacementCode).to.eql('foo'); - expect(secondPlacementCode).to.eql('bar'); - }); - - it('should add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'aardvark'); - expect(secondBid).to.have.property('bidderCode', 'aardvark'); - }); - }); - - describe('add bids to the manager', () => { - let firstBid; - let secondBid; - - beforeEach(() => { - sandbox.stub(bidmanager, 'addBidResponse'); - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - aardvark.callBids(bidderRequest); - - $$PREBID_GLOBAL$$.aardvarkResponse(bidderResponse); - firstBid = bidmanager.addBidResponse.firstCall.args[1]; - secondBid = bidmanager.addBidResponse.secondCall.args[1]; - }); - - it('should add a bid object for each bid', () => { - sinon.assert.calledTwice(bidmanager.addBidResponse); - }); - - it('should pass the correct placement code as first param', () => { - let firstPlacementCode = bidmanager.addBidResponse.firstCall.args[0]; - let secondPlacementCode = bidmanager.addBidResponse.secondCall.args[0]; - - expect(firstPlacementCode).to.eql('foo'); - expect(secondPlacementCode).to.eql('bar'); - }); - - it('should include bid request bidId as the adId', () => { - expect(firstBid).to.have.property('adId', 'bidId1'); - expect(secondBid).to.have.property('adId', 'bidId2'); - }); - - it('should have a good statusCode', () => { - expect(firstBid.getStatusCode()).to.eql(constants.STATUS.GOOD); - expect(secondBid.getStatusCode()).to.eql(constants.STATUS.GOOD); - }); - - it('should add the CPM to the bid object', () => { - expect(firstBid).to.have.property('cpm', 0.39440); - expect(secondBid).to.have.property('cpm', 0.03485); - }); - - it('should add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'aardvark'); - expect(secondBid).to.have.property('bidderCode', 'aardvark'); - }); - - it('should include the ad to the bid object', () => { - expect(firstBid).to.have.property('ad'); - expect(secondBid).to.have.property('ad'); - }); - - it('should include the size to the bid object', () => { - expect(firstBid).to.have.property('width', 728); - expect(firstBid).to.have.property('height', 90); - expect(secondBid).to.have.property('width', 300); - expect(secondBid).to.have.property('height', 600); - }); - }); -}); diff --git a/test/spec/modules/adbladeBidAdapter_spec.js b/test/spec/modules/adbladeBidAdapter_spec.js deleted file mode 100644 index ec3c9f6b23e..00000000000 --- a/test/spec/modules/adbladeBidAdapter_spec.js +++ /dev/null @@ -1,206 +0,0 @@ -import {expect} from 'chai'; -import Adapter from '../../../modules/adbladeBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import adLoader from '../../../src/adloader'; - -describe('adblade adapter', () => { - 'use strict'; - - let bidsRequestedOriginal; - let adapter; - let sandbox; - - const bidderRequest = { - bidderCode: 'adblade', - bids: [ - { - bidId: 'bidId1', - bidder: 'adblade', - placementCode: 'foo', - sizes: [[728, 90]], - params: { - partnerId: 1, - } - } - ] - }; - - beforeEach(() => { - bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; - $$PREBID_GLOBAL$$._bidsRequested = []; - - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); - - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; - }); - - describe('sizes', () => { - beforeEach(() => { - sandbox.stub(adLoader, 'loadScript'); - }); - - let bidderRequest = { - bidderCode: 'adblade', - bids: [ - { - bidId: 'bidId1', - bidder: 'adblade', - placementCode: 'foo', - sizes: [[728, 90], [300, 250]], - params: { - partnerId: 1, - } - } - ] - }; - - it('array of arrays', () => { - adapter.callBids(bidderRequest); - sinon.assert.calledTwice(adLoader.loadScript); - - expect(adLoader.loadScript.firstCall.args[0]).to.include('%22banner%22%3A%7B%22w%22%3A728%2C%22h%22%3A90%7D%2C'); // banner:{w:728, h:90} - expect(adLoader.loadScript.firstCall.args[0]).to.include('adblade.com'); - expect(adLoader.loadScript.firstCall.args[0]).to.include('prebidjs'); - - expect(adLoader.loadScript.secondCall.args[0]).to.include('%22banner%22%3A%7B%22w%22%3A300%2C%22h%22%3A250%7D%2C'); // banner:{w:300, h:250} - expect(adLoader.loadScript.secondCall.args[0]).to.include('adblade.com'); - expect(adLoader.loadScript.secondCall.args[0]).to.include('prebidjs'); - }); - - it('array of strings', () => { - bidderRequest.bids[0].sizes = [728, 90]; - adapter.callBids(bidderRequest); - sinon.assert.calledOnce(adLoader.loadScript); - - expect(adLoader.loadScript.firstCall.args[0]).to.include('%22banner%22%3A%7B%22w%22%3A728%2C%22h%22%3A90%7D%2C'); // banner:{w:728, h:90} - expect(adLoader.loadScript.firstCall.args[0]).to.include('adblade.com'); - expect(adLoader.loadScript.firstCall.args[0]).to.include('prebidjs'); - }); - }); - - describe('callBids', () => { - beforeEach(() => { - sandbox.stub(adLoader, 'loadScript'); - adapter.callBids(bidderRequest); - }); - - it('should load script', () => { - sinon.assert.calledOnce(adLoader.loadScript); - - expect(adLoader.loadScript.firstCall.args[0]).to.include('adblade.com'); - expect(adLoader.loadScript.firstCall.args[0]).to.include('prebidjs'); - }); - }); - - describe('adbladeResponse', () => { - it('should exist and be a function', () => { - expect($$PREBID_GLOBAL$$.adbladeResponse).to.exist.and.to.be.a('function'); - }); - }); - - describe('add bids to the manager', () => { - let firstBid; - - beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - - // respond - let bidderReponse = { - 'cur': 'USD', - 'id': '03a9404f-7b39-4d04-b50b-6459b9aa3ffa', - 'seatbid': [ - { - 'seat': '1', - 'bid': [ - { - 'nurl': 'http://example.com', - 'crid': '20063', - 'adomain': [ - 'www.adblade.com' - ], - 'price': 3, - 'w': 728, - 'h': 90, - 'id': '1', - 'adm': '
', - 'impid': 'bidId1', - 'cid': '99' - } - ] - } - ] - }; - $$PREBID_GLOBAL$$.adbladeResponse(bidderReponse); - - firstBid = bidManager.addBidResponse.firstCall.args[1]; - }); - - it('should add a bid object for each bid', () => { - sinon.assert.calledOnce(bidManager.addBidResponse); - }); - - it('should pass the correct placement code as first param', () => { - let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; - - expect(firstPlacementCode).to.eql('foo'); - }); - - it('should have a good statusCode', () => { - expect(firstBid.getStatusCode()).to.eql(1); - }); - - it('should add the CPM to the bid object', () => { - expect(firstBid).to.have.property('cpm', 3); - }); - - it('should include the ad to the bid object', () => { - expect(firstBid).to.have.property('ad'); - }); - - it('should include the size to the bid object', () => { - expect(firstBid).to.have.property('width', 728); - expect(firstBid).to.have.property('height', 90); - }); - }); - - describe('add empty bids if no bid returned', () => { - let firstBid; - - beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - - // respond - let bidderReponse = {}; - $$PREBID_GLOBAL$$.adbladeResponse(bidderReponse); - - firstBid = bidManager.addBidResponse.firstCall.args[1]; - }); - - it('should add a bid object for each bid', () => { - sinon.assert.calledOnce(bidManager.addBidResponse); - }); - - it('should have an error statusCode', () => { - expect(firstBid.getStatusCode()).to.eql(2); - }); - - it('should pass the correct placement code as first param', () => { - let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; - - expect(firstPlacementCode).to.eql('foo'); - }); - - it('should add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'adblade'); - }); - }); -}); diff --git a/test/spec/modules/adbundBidAdapter_spec.js b/test/spec/modules/adbundBidAdapter_spec.js deleted file mode 100644 index da9b2e2e9b9..00000000000 --- a/test/spec/modules/adbundBidAdapter_spec.js +++ /dev/null @@ -1,94 +0,0 @@ -import { expect } from 'chai'; -import Adapter from '../../../modules/adbundBidAdapter'; -import bidManager from 'src/bidmanager'; -import CONSTANTS from 'src/constants.json'; - -describe('adbund adapter tests', function () { - let sandbox; - let adapter; - let server; - - const request = { - bidderCode: 'adbund', - bids: [{ - bidder: 'adbund', - params: { - sid: '110238', - bidfloor: 0.036 - }, - placementCode: 'adbund', - sizes: [[300, 250]], - bidId: 'adbund_bidId', - bidderRequestId: 'adbund_bidderRequestId', - requestId: 'adbund_requestId' - }] - }; - - const response = { - bidderCode: 'adbund', - cpm: 1.06, - height: 250, - width: 300 - }; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('adbund callBids validation', () => { - beforeEach(() => { - adapter = new Adapter(); - }); - - afterEach(() => { - }); - - it('Valid bid-request', () => { - let bidderRequest; - - sandbox.stub(adapter, 'callBids'); - adapter.callBids(request); - - bidderRequest = adapter.callBids.getCall(0).args[0]; - - expect(bidderRequest).to.have.property('bids') - .that.is.an('array') - .with.lengthOf(1); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .to.have.property('bidder', 'adbund'); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .with.property('sizes') - .that.is.an('array') - .with.lengthOf(1) - .that.deep.equals(request.bids[0].sizes); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .with.property('params') - .to.have.property('bidfloor', 0.036); - }); - - it('Valid bid-response', () => { - var bidderResponse; - - sandbox.stub(bidManager, 'addBidResponse'); - adapter.callBids(request); - bidderResponse = bidManager.addBidResponse.getCall(0) || - bidManager.addBidResponse.getCall(1); - - if (bidderResponse && bidderResponse.args && bidderResponse.args[1]) { - bidderResponse = bidderResponse.args[1]; - expect(bidderResponse.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidderResponse.bidderCode).to.equal(response.bidderCode); - expect(bidderResponse.width).to.equal(response.width); - expect(bidderResponse.height).to.equal(response.height); - expect(bidderResponse.cpm).to.equal(response.cpm); - } - }); - }); -}); diff --git a/test/spec/modules/adbutlerBidAdapter_spec.js b/test/spec/modules/adbutlerBidAdapter_spec.js index d026ac8de98..a46039402e6 100644 --- a/test/spec/modules/adbutlerBidAdapter_spec.js +++ b/test/spec/modules/adbutlerBidAdapter_spec.js @@ -1,516 +1,211 @@ -describe('adbutler adapter tests', function () { - var expect = require('chai').expect; - var adapter = require('modules/adbutlerBidAdapter'); - var adLoader = require('src/adloader'); - var bidmanager = require('src/bidmanager'); +import {expect} from 'chai'; +import {spec} from 'modules/adbutlerBidAdapter'; - describe('creation of bid url', function () { - var stubLoadScript; +describe('AdButler adapter', () => { + let bidRequests; - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - - afterEach(function () { - stubLoadScript.restore(); - }); - - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; - } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; - } - if (typeof ($$PREBID_GLOBAL$$._adsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._adsReceived = []; - } - - it('should be called', function () { - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - adapter().callBids(params); - - sinon.assert.called(stubLoadScript); - }); - - it('should populate the keyword', function() { - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093', - keyword: 'fish' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - adapter().callBids(params); - - var requestURI = stubLoadScript.getCall(0).args[0]; - - expect(requestURI).to.have.string(';kw=fish;'); - }); - - it('should use custom domain string', function() { - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '107878', - zoneID: '86133', - domain: 'servedbyadbutler.com.dan.test' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - ] - }; - - adapter().callBids(params); - - var requestURI = stubLoadScript.getCall(0).args[0]; - - expect(requestURI).to.have.string('.dan.test'); - }); - }); - describe('bid responses', function() { - it('should return complete bid response', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bidderCode: 'adbutler', + beforeEach(() => { + bidRequests = [ + { bidder: 'adbutler', - bids: [ - { - bidId: '3c94018cdbf2f68-1', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093', - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 1.5, - width: 300, - height: 250, - place: 0 - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); + params: { + accountID: '167283', + zoneID: '210093', + keyword: 'red', + minCPM: '1.00', + maxCPM: '5.00' + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[300, 250], [300, 600]], + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('/123456/header-bid-tag-1'); - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.bidderCode).to.equal('adbutler'); - expect(bidObject1.cpm).to.equal(1.5); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.height).to.equal(250); - - stubAddBidResponse.restore(); - }); - - it('should return empty bid response', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-2', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210085', - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'NO_ELIGIBLE_ADS', - zone_id: 210085, - width: 728, - height: 90, - place: 0 - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('/123456/header-bid-tag-1'); - expect(bidObject1.getStatusCode()).to.equal(2); - expect(bidObject1.bidderCode).to.equal('adbutler'); - - stubAddBidResponse.restore(); - }); - - it('should return empty bid response on incorrect size', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-3', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210085', - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210085, - cpm: 1.5, - width: 728, - height: 90, - place: 0 - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject1.getStatusCode()).to.equal(2); - - stubAddBidResponse.restore(); - }); - - it('should return empty bid response with CPM too low', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-4', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093', - minCPM: '5.00' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 1.5, - width: 300, - height: 250, - place: 0 - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject1.getStatusCode()).to.equal(2); - - stubAddBidResponse.restore(); - }); - - it('should return empty bid response with CPM too high', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-5', - sizes: [[300, 250]], - bidder: 'adbutler', - params: { - accountID: '167283', - zoneID: '210093', - maxCPM: '1.00' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 1.5, - width: 300, - height: 250, - place: 0 - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject1.getStatusCode()).to.equal(2); - - stubAddBidResponse.restore(); - }); + ]; }); - describe('ad code', function() { - it('should be populated', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-6', - sizes: [[300, 250]], + describe('implementation', () => { + describe('for requests', () => { + it('should accept valid bid', () => { + let validBid = { bidder: 'adbutler', params: { accountID: '167283', zoneID: '210093' - }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 1.5, - width: 300, - height: 250, - place: 0, - ad_code: '' - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); + } + }, + isValid = spec.isBidRequestValid(validBid); - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.ad).to.have.length.above(1); - - stubAddBidResponse.restore(); - }); + expect(isValid).to.equal(true); + }); - it('should contain tracking pixels', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bidderCode: 'adbutler', - bids: [ - { - bidId: '3c9408cdbf2f68-7', - sizes: [[300, 250]], + it('should reject invalid bid', () => { + let invalidBid = { bidder: 'adbutler', params: { accountID: '167283', - zoneID: '210093' + } + }, + isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should use custom domain string', () => { + let bidRequests = [ + { + bidId: '3c9408cdbf2f68', + sizes: [[300, 250]], + bidder: 'adbutler', + params: { + accountID: '107878', + zoneID: '86133', + domain: 'servedbyadbutler.com.dan.test' + }, + auctionId: '10b327aa396609', + placementCode: '/123456/header-bid-tag-1' + } + ], + requests = spec.buildRequests(bidRequests), + requestURL = requests[0].url; + + expect(requestURL).to.have.string('.dan.test'); + }); + + it('should set default domain', () => { + let requests = spec.buildRequests(bidRequests), + request = requests[0]; + + let [domain] = request.url.split('/adserve/'); + + expect(domain).to.equal('http://servedbyadbutler.com'); + }); + + it('should set the keyword parameter', () => { + let requests = spec.buildRequests(bidRequests), + requestURL = requests[0].url; + + expect(requestURL).to.have.string(';kw=red;'); + }); + + it('should increment the count for the same zone', () => { + let bidRequests = [ + { + sizes: [[300, 250]], + bidder: 'adbutler', + params: { + accountID: '107878', + zoneID: '86133', + domain: 'servedbyadbutler.com.dan.test' + } + }, { + sizes: [[300, 250]], + bidder: 'adbutler', + params: { + accountID: '107878', + zoneID: '86133', + domain: 'servedbyadbutler.com.dan.test' + } }, - requestId: '10b327aa396609', - placementCode: '/123456/header-bid-tag-1' - } - - ] - }; - - var response = { - status: 'SUCCESS', - account_id: 167283, - zone_id: 210093, - cpm: 1.5, - width: 300, - height: 250, - place: 0, - ad_code: '', - tracking_pixels: [ - 'http://tracking.pixel.com/params=info' - ] - }; - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.adbutlerCB(response); - - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.ad).to.have.string('http://tracking.pixel.com/params=info'); + ], + requests = spec.buildRequests(bidRequests), + firstRequest = requests[0].url, + secondRequest = requests[1].url; + + expect(firstRequest).to.have.string(';place=0;'); + expect(secondRequest).to.have.string(';place=1;'); + }); + }); - stubAddBidResponse.restore(); + describe('bid responses', () => { + it('should return complete bid response', () => { + let serverResponse = { + body: { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210093, + cpm: 1.5, + width: 300, + height: 250, + place: 0, + ad_code: '', + tracking_pixels: [ + 'http://tracking.pixel.com/params=info' + ] + } + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(1); + + expect(bids[0].bidderCode).to.equal('adbutler'); + expect(bids[0].cpm).to.equal(1.5); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ad).to.have.length.above(1); + expect(bids[0].ad).to.have.string('http://tracking.pixel.com/params=info'); + }); + + it('should return empty bid response', () => { + let serverResponse = { + status: 'NO_ELIGIBLE_ADS', + zone_id: 210083, + width: 300, + height: 250, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on incorrect size', () => { + let serverResponse = { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210083, + cpm: 1.5, + width: 728, + height: 90, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response with CPM too low', () => { + let serverResponse = { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210093, + cpm: 0.75, + width: 300, + height: 250, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response with CPM too high', () => { + let serverResponse = { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210093, + cpm: 7.00, + width: 300, + height: 250, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); }); }); }); diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js index 9d77b4faca5..21ff84bdad5 100644 --- a/test/spec/modules/adformBidAdapter_spec.js +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -1,161 +1,354 @@ -import { assert } from 'chai'; -import * as utils from 'src/utils'; -import adLoader from 'src/adloader'; -import bidManager from 'src/bidmanager'; -import AdformAdapter from 'modules/adformBidAdapter'; +import {assert, expect} from 'chai'; +import * as url from 'src/url'; +import {spec} from 'modules/adformBidAdapter'; +import { BANNER, VIDEO } from 'src/mediaTypes'; describe('Adform adapter', () => { - let _adformAdapter, sandbox; + let serverResponse, bidRequest, bidResponses; + let bids = []; + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'adform', + 'params': { + 'mid': '19910113' + } + }; - describe('request', () => { - it('should create callback method on PREBID_GLOBAL', () => { - assert.typeOf($$PREBID_GLOBAL$$._adf_callback, 'function'); + it('should return true when required params found', () => { + assert(spec.isBidRequestValid(bid)); }); - it('should pass multiple bids via single request', () => { - const _request = adLoader.loadScript; + it('should return false when required params are missing', () => { + bid.params = { + adxDomain: 'adx.adform.net' + }; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); - assert(_request.calledOnce); - assert.lengthOf(_request.args[0], 1); - assert.lengthOf(parseUrl(_request.args[0][0]).items, 3); + describe('buildRequests', () => { + it('should pass multiple bids via single request', () => { + let request = spec.buildRequests(bids); + let parsedUrl = parseUrl(request.url); + assert.lengthOf(parsedUrl.items, 7); }); it('should handle global request parameters', () => { - const _request = parseUrl(adLoader.loadScript.args[0][0]); - const _query = _request.query; + let parsedUrl = parseUrl(spec.buildRequests([bids[0]]).url); + let query = parsedUrl.query; - assert.equal(_request.path, '//newdomain/adx'); - assert.equal(_query.callback.split('.')[1], '_adf_callback'); - assert.equal(_query.tid, 145); - assert.equal(_query.rp, 4); - assert.equal(_query.fd, 1); - assert.equal(_query.url, encodeURIComponent('some// there')); + assert.equal(parsedUrl.path, '//newDomain/adx'); + assert.equal(query.tid, 45); + assert.equal(query.rp, 4); + assert.equal(query.fd, 1); + assert.equal(query.stid, '7aefb970-2045'); + assert.equal(query.url, encodeURIComponent('some// there')); + }); + + it('should set correct request method', () => { + let request = spec.buildRequests([bids[0]]); + assert.equal(request.method, 'GET'); }); it('should correctly form bid items', () => { - const _items = parseUrl(adLoader.loadScript.args[0][0]).items; + let bidList = bids; + let request = spec.buildRequests(bidList); + let parsedUrl = parseUrl(request.url); + assert.deepEqual(parsedUrl.items, [ + { + mid: '1', + transactionId: '5f33781f-9552-4ca1' + }, + { + mid: '2', + someVar: 'someValue', + pt: 'gross', + transactionId: '5f33781f-9552-4iuy' + }, + { + mid: '3', + pdom: 'home', + transactionId: '5f33781f-9552-7ev3' + }, + { + mid: '3', + pdom: 'home', + transactionId: '5f33781f-9552-7ev3' + }, + { + mid: '3', + pdom: 'home', + transactionId: '5f33781f-9552-7ev3' + }, + { + mid: '5', + pt: 'net', + transactionId: '5f33781f-9552-7ev3', + }, + { + mid: '6', + pt: 'gross', + transactionId: '5f33781f-9552-7ev3' + } + ]); + }); + + it('should not change original validBidRequests object', () => { + var resultBids = JSON.parse(JSON.stringify(bids[0])); + let request = spec.buildRequests([bids[0]]); + assert.deepEqual(resultBids, bids[0]); + }); + + it('should send GDPR Consent data to adform', () => { + var resultBids = JSON.parse(JSON.stringify(bids[0])); + let request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: 1, consentString: 'concentDataString'}}); + let parsedUrl = parseUrl(request.url).query; + + assert.equal(parsedUrl.gdpr, 1); + assert.equal(parsedUrl.gdpr_consent, 'concentDataString'); + }); + + it('should set gross to the request, if there is any gross priceType', () => { + let request = spec.buildRequests([bids[5], bids[5]]); + let parsedUrl = parseUrl(request.url); + + assert.equal(parsedUrl.query.pt, 'net'); - assert.deepEqual(_items[0], { mid: '1', transactionId: 'transactionId' }); - assert.deepEqual(_items[1], { mid: '2', someVar: 'someValue', transactionId: 'transactionId' }); - assert.deepEqual(_items[2], { mid: '3', pdom: 'home', transactionId: 'transactionId' }); + request = spec.buildRequests([bids[4], bids[3]]); + parsedUrl = parseUrl(request.url); + + assert.equal(parsedUrl.query.pt, 'gross'); }); }); - describe('response callback', () => { - it('should create bid response item for every requested item', () => { - assert(bidManager.addBidResponse.calledThrice); + describe('interpretResponse', () => { + it('should respond with empty response when there is empty serverResponse', () => { + let result = spec.interpretResponse({ body: {} }, {}); + assert.deepEqual(result, []); + }); + it('should respond with empty response when sizes doesn\'t match', () => { + serverResponse.body[0].response = 'banner'; + serverResponse.body[0].width = 100; + serverResponse.body[0].height = 150; + + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[0]]; + let result = spec.interpretResponse(serverResponse, bidRequest); + + assert.equal(serverResponse.body.length, 1); + assert.equal(serverResponse.body[0].response, 'banner'); + assert.deepEqual(result, []); }); + it('should respond with empty response when response from server is not banner', () => { + serverResponse.body[0].response = 'not banner'; + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[0]]; + let result = spec.interpretResponse(serverResponse, bidRequest); - it('should correctly form bid response object', () => { - const _bid = bidManager.addBidResponse.firstCall.args; - const _bidObject = _bid[1]; + assert.deepEqual(result, []); + }); + it('should interpret server response correctly with one bid', () => { + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[0]]; + let result = spec.interpretResponse(serverResponse, bidRequest)[0]; - assert.equal(_bid[0], 'code-1'); - assert.equal(_bidObject.statusMessage, 'Bid available'); - assert.equal(_bidObject.bidderCode, 'adform'); - assert.equal(_bidObject.cpm, 1.1); - assert.equal(_bidObject.cur, 'EUR'); - assert.equal(_bidObject.ad, ''); - assert.equal(_bidObject.width, 90); - assert.equal(_bidObject.height, 90); - assert.equal(_bidObject.dealId, 'deal-1'); - assert.equal(_bidObject.transactionId, 'transactionId'); + assert.equal(result.requestId, '2a0cf4e'); + assert.equal(result.cpm, 13.9); + assert.equal(result.width, 300); + assert.equal(result.height, 250); + assert.equal(result.creativeId, '2a0cf4e'); + assert.equal(result.dealId, '123abc'); + assert.equal(result.currency, 'EUR'); + assert.equal(result.netRevenue, true); + assert.equal(result.ttl, 360); + assert.equal(result.ad, ''); + assert.equal(result.bidderCode, 'adform'); + assert.equal(result.transactionId, '5f33781f-9552-4ca1'); }); - it('should correctly form empty bid response object', () => { - const _bid = bidManager.addBidResponse.secondCall.args; - const _bidObject = _bid[1]; + it('should set correct netRevenue', () => { + serverResponse.body = [serverResponse.body[0]]; + bidRequest.bids = [bidRequest.bids[1]]; + bidRequest.netRevenue = 'gross'; + let result = spec.interpretResponse(serverResponse, bidRequest)[0]; - assert.equal(_bid[0], 'code-2'); - assert.equal(_bidObject.statusMessage, 'Bid returned empty or error response'); - assert.equal(_bidObject.bidderCode, 'adform'); + assert.equal(result.netRevenue, false); }); - it('should filter out item which does not fit required size', () => { - const _bid = bidManager.addBidResponse.thirdCall.args; - const _bidObject = _bid[1]; + it('should create bid response item for every requested item', () => { + let result = spec.interpretResponse(serverResponse, bidRequest); + assert.lengthOf(result, 5); + }); - assert.equal(_bid[0], 'code-3'); - assert.equal(_bidObject.statusMessage, 'Bid returned empty or error response'); - assert.equal(_bidObject.bidderCode, 'adform'); + it('should create bid response with vast xml', () => { + const result = spec.interpretResponse(serverResponse, bidRequest)[3]; + assert.equal(result.vastXml, ''); }); - it('should correctly set bid response adId', () => { - const addResponse = bidManager.addBidResponse; - assert.equal('abc', addResponse.getCall(0).args[1].adId); - assert.equal('123', addResponse.getCall(1).args[1].adId); - assert.equal('a1b', addResponse.getCall(2).args[1].adId); + it('should create bid response with vast url', () => { + const result = spec.interpretResponse(serverResponse, bidRequest)[4]; + assert.equal(result.vastUrl, 'vast://url'); }); - beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); - $$PREBID_GLOBAL$$._adf_callback([ + it('should set mediaType on bid response', () => { + const expected = [ BANNER, BANNER, BANNER, VIDEO, VIDEO ]; + const result = spec.interpretResponse(serverResponse, bidRequest); + for (let i = 0; i < result.length; i++) { + assert.equal(result[i].mediaType, expected[i]); + } + }); + + it('should set default netRevenue as gross', () => { + bidRequest.netRevenue = 'gross'; + const result = spec.interpretResponse(serverResponse, bidRequest); + for (let i = 0; i < result.length; i++) { + assert.equal(result[i].netRevenue, false); + } + }); + }); + + beforeEach(() => { + 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'}]; + bids = [ + { + adUnitCode: placementCode[0], + auctionId: '7aefb970-2045', + bidId: '2a0cf4e', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[0], + adxDomain: 'newDomain', + tid: 45, + placementCode: placementCode[0], + sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], + transactionId: '5f33781f-9552-4ca1' + }, + { + adUnitCode: placementCode[1], + auctionId: '7aefb970-2045', + bidId: '2a0cf5b', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[1], + placementCode: placementCode[1], + sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], + transactionId: '5f33781f-9552-4iuy' + }, + { + adUnitCode: placementCode[2], + auctionId: '7aefb970-2045', + bidId: '2a0cf6n', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[2], + placementCode: placementCode[2], + sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], + transactionId: '5f33781f-9552-7ev3' + }, + { + adUnitCode: placementCode[3], + auctionId: '7aefb970-2045', + bidId: '2a0cf6n', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[2], + placementCode: placementCode[2], + sizes: [], + transactionId: '5f33781f-9552-7ev3' + }, + { + adUnitCode: placementCode[4], + auctionId: '7aefb970-2045', + bidId: '2a0cf6n', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[2], + placementCode: placementCode[2], + sizes: [], + transactionId: '5f33781f-9552-7ev3' + }, + { + adUnitCode: placementCode[4], + auctionId: '7aefb970-2045', + bidId: '2a0cf6n', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[3], + placementCode: placementCode[2], + sizes: [], + transactionId: '5f33781f-9552-7ev3' + }, + { + adUnitCode: placementCode[4], + auctionId: '7aefb970-2045', + bidId: '2a0cf6n', + bidder: 'adform', + bidderRequestId: '1ab8d9', + params: params[4], + placementCode: placementCode[2], + sizes: [], + transactionId: '5f33781f-9552-7ev3' + } + ]; + serverResponse = { + body: [ { + banner: '', + deal_id: '123abc', + height: 250, response: 'banner', - width: 90, - height: 90, - banner: '', - win_bid: 1.1, - win_cur: 'EUR', - deal_id: 'deal-1' + width: 300, + win_bid: 13.9, + win_cur: 'EUR' }, - {}, { + banner: '', + deal_id: '123abc', + height: 300, response: 'banner', - width: 50, - height: 50, - banner: '' - } - ]); - }); - }); - - beforeEach(() => { - var transactionId = 'transactionId'; - _adformAdapter = new AdformAdapter(); - utils.getUniqueIdentifierStr = () => 'callback'; - sandbox = sinon.sandbox.create(); - sandbox.stub(adLoader, 'loadScript'); - _adformAdapter.callBids({ - bids: [ + width: 250, + win_bid: 13.9, + win_cur: 'EUR' + }, { - bidId: 'abc', - placementCode: 'code-1', - sizes: [ [ 100, 100], [ 90, 90 ] ], - params: { - mid: 1, - url: 'some// there' - }, - adxDomain: 'newdomain', - tid: 45, - transactionId: transactionId + banner: '', + deal_id: '123abc', + height: 300, + response: 'banner', + width: 600, + win_bid: 10, + win_cur: 'EUR' }, { - bidId: '123', - placementCode: 'code-2', - sizes: [ [ 100, 100] ], - params: { - mid: 2, - tid: 145, - someVar: 'someValue' - }, - transactionId: transactionId + deal_id: '123abc', + height: 300, + response: 'vast_content', + width: 600, + win_bid: 10, + win_cur: 'EUR', + vast_content: '' }, { - bidId: 'a1b', - placementCode: 'code-3', - sizes: [ [ 50, 40], [ 40, 50 ] ], - params: { - mid: 3, - pdom: 'home' - }, - transactionId: transactionId + deal_id: '123abc', + height: 300, + response: 'vast_url', + width: 600, + win_bid: 10, + win_cur: 'EUR', + vast_url: 'vast://url' } - ]}); - }); - - afterEach(() => { - sandbox.restore(); + ], + headers: {} + }; + bidRequest = { + bidder: 'adform', + bids: bids, + method: 'GET', + url: 'url', + netRevenue: 'net' + }; }); }); @@ -166,7 +359,7 @@ function parseUrl(url) { path: parts.join('/'), items: query .filter((i) => !~i.indexOf('=')) - .map((i) => fromBase64(i) + .map((i) => atob(decodeURIComponent(i)) .split('&') .reduce(toObject, {})), query: query @@ -176,18 +369,6 @@ function parseUrl(url) { }; } -function fromBase64(input) { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'.split(''); - let bc = 0, bs, buffer, idx = 0, output = ''; - for (; buffer = input.charAt(idx++); - ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, - bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 - ) { - buffer = chars.indexOf(buffer); - } - return output; -} - function toObject(cache, string) { const keyValue = string.split('='); cache[keyValue[0]] = keyValue[1]; diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js new file mode 100644 index 00000000000..4239712ccaf --- /dev/null +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -0,0 +1,385 @@ +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'; + +describe('AdgenerationAdapter', () => { + const adapter = newBidder(spec); + const ENDPOINT = ['http://api-test.scaleout.jp/adsv/v1', 'https://d.socdm.com/adsv/v1']; + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'adg', + 'params': { + id: '58278', // banner + } + }; + 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', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const bidRequests = [ + { // banner + bidder: 'adg', + params: { + id: '58278', + width: '300', + height: '250' + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + }, + { // native + bidder: 'adg', + params: { + id: '58278', + width: '300', + height: '250' + }, + mediaTypes: { + native: { + image: { + required: true + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + }, + icon: { + required: true + } + }, + }, + adUnitCode: 'adunit-code', + sizes: [[1, 1]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + } + ]; + 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' + }; + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(ENDPOINT[1]); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to debug ENDPOINT via GET', () => { + bidRequests[0].params.debug = true; + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(ENDPOINT[0]); + expect(request.method).to.equal('GET'); + }); + + it('should attache params to the banner request', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data).to.equal(data.banner); + }); + + it('should attache params to the native request', () => { + const request = spec.buildRequests(bidRequests)[1]; + expect(request.data).to.equal(data.native); + }); + }); + + describe('interpretResponse', () => { + const bidRequests = { + banner: { + bidRequest: { + bidder: 'adg', + params: { + id: '58278', // banner + }, + adUnitCode: 'adunit-code', + sizes: [[320, 100]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + }, + }, + native: { + bidRequest: { + bidder: 'adg', + params: { + id: '58278', // banner + }, + mediaTypes: { + native: { + image: { + required: true + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + }, + icon: { + required: true + } + } + }, + adUnitCode: 'adunit-code', + sizes: [[1, 1]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + }, + }, + }; + + const serverResponse = { + noAd: { + results: [], + }, + banner: { + ad: '
', + beacon: '', + cpm: 36.0008, + displaytype: '1', + ids: {}, + w: 320, + h: 100, + location_params: null, + locationid: '58279', + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000, + results: [ + {ad: '
'}, + ] + }, + native: { + ad: '↵ ↵ ↵ ↵ ↵
↵ ', + beacon: '', + cpm: 36.0008, + displaytype: '1', + ids: {}, + location_params: null, + locationid: '58279', + native_ad: { + assets: [ + { + data: { + label: 'accompanying_text', + value: 'AD' + }, + id: 501 + }, + { + data: { + label: 'optout_url', + value: 'https://supership.jp/optout/' + }, + id: 502 + }, + { + data: { + ext: { + black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', + }, + label: 'information_icon_url', + value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', + id: 503 + } + }, + { + id: 1, + required: 1, + title: {text: 'Title'} + }, + { + id: 2, + img: { + h: 250, + url: 'https://s3-ap-northeast-1.amazonaws.com/sdk-temp/adg-sample-ad/img/300x250.png', + w: 300 + }, + required: 1 + }, + { + id: 3, + img: { + h: 300, + url: 'https://placehold.jp/300x300.png', + w: 300 + }, + required: 1 + }, + { + data: {value: 'Description'}, + id: 5, + required: 0 + }, + { + data: {value: 'CTA'}, + id: 6, + required: 0 + }, + { + data: {value: 'Sponsored'}, + id: 4, + required: 0 + } + ], + imptrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1.gif'], + link: { + clicktrackers: [ + 'https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1_clicktracker_access.gif' + ], + url: 'https://supership.jp' + }, + }, + results: [ + {ad: 'Creative<\/body>'} + ], + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000 + } + }; + + const bidResponses = { + banner: { + requestId: '2f6ac468a9c15e', + cpm: 36.0008, + width: 320, + height: 100, + creativeId: '1k2kv35vsa5r', + dealId: 'fd5sa5fa7f', + currency: 'JPY', + netRevenue: true, + ttl: 1000, + referrer: utils.getTopWindowUrl(), + ad: '
', + }, + native: { + requestId: '2f6ac468a9c15e', + cpm: 36.0008, + width: 1, + height: 1, + creativeId: '1k2kv35vsa5r', + dealId: 'fd5sa5fa7f', + currency: 'JPY', + netRevenue: true, + ttl: 1000, + referrer: utils.getTopWindowUrl(), + ad: '↵
', + native: { + title: 'Title', + image: { + url: 'https://s3-ap-northeast-1.amazonaws.com/sdk-temp/adg-sample-ad/img/300x250.png', + height: 250, + width: 300 + }, + icon: { + url: 'https://placehold.jp/300x300.png', + height: 300, + width: 300 + }, + sponsoredBy: 'Sponsored', + body: 'Description', + cta: 'CTA', + 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'] + }, + mediaType: NATIVE + } + }; + + it('no bid responses', () => { + const result = spec.interpretResponse({body: serverResponse.noAd}, bidRequests.banner); + expect(result.length).to.equal(0); + }); + + it('handles banner responses', () => { + const result = spec.interpretResponse({body: serverResponse.banner}, bidRequests.banner)[0]; + expect(result.requestId).to.equal(bidResponses.banner.requestId); + expect(result.width).to.equal(bidResponses.banner.width); + expect(result.height).to.equal(bidResponses.banner.height); + expect(result.creativeId).to.equal(bidResponses.banner.creativeId); + expect(result.dealId).to.equal(bidResponses.banner.dealId); + 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); + }); + + it('handles native responses', () => { + const result = spec.interpretResponse({body: serverResponse.native}, bidRequests.native)[0]; + expect(result.requestId).to.equal(bidResponses.native.requestId); + expect(result.width).to.equal(bidResponses.native.width); + expect(result.height).to.equal(bidResponses.native.height); + expect(result.creativeId).to.equal(bidResponses.native.creativeId); + expect(result.dealId).to.equal(bidResponses.native.dealId); + 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); + expect(result.native.image.width).to.equal(bidResponses.native.native.image.width); + expect(result.native.icon.url).to.equal(bidResponses.native.native.icon.url); + expect(result.native.icon.width).to.equal(bidResponses.native.native.icon.width); + expect(result.native.icon.height).to.equal(bidResponses.native.native.icon.height); + 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(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]); + expect(result.mediaType).to.equal(bidResponses.native.mediaType); + }); + }); +}); diff --git a/test/spec/modules/adkernelAdnAnalyticsAdapter_spec.js b/test/spec/modules/adkernelAdnAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..f5d1a5d02f1 --- /dev/null +++ b/test/spec/modules/adkernelAdnAnalyticsAdapter_spec.js @@ -0,0 +1,269 @@ +import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/adkernelAdnAnalyticsAdapter'; +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'); + +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('', () => { + let sandbox; + + before(() => { + sandbox = sinon.sandbox.create(); + }); + + after(() => { + sandbox.restore(); + analyticsAdapter.disableAnalytics(); + }); + + describe('UTM source parser', () => { + let stubSetItem; + let stubGetItem; + + before(() => { + stubSetItem = sandbox.stub(storage, 'setItem'); + stubGetItem = sandbox.stub(storage, 'getItem'); + }); + + afterEach(() => { + sandbox.reset(); + }); + + it('should parse first direct visit as (direct)', () => { + stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); + stubSetItem.returns(undefined); + let source = getUmtSource('http://example.com'); + expect(source).to.be.eql(DIRECT); + }); + + it('should respect past campaign visits before direct', () => { + stubGetItem.withArgs('adk_dpt_analytics').returns(JSON.stringify(CAMPAIGN)); + stubSetItem.returns(undefined); + let source = getUmtSource('http://example.com'); + expect(source).to.be.eql(CAMPAIGN); + }); + + it('should parse visit from google as organic', () => { + 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 respect previous campaign visit before organic', () => { + stubGetItem.withArgs('adk_dpt_analytics').returns(JSON.stringify(CAMPAIGN)); + stubSetItem.returns(undefined); + let source = getUmtSource('http://example.com', 'https://www.google.com/search?q=pikachu'); + expect(source).to.be.eql(CAMPAIGN); + }); + + it('should parse referral visit', () => { + 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 respect previous campaign visit before referral', () => { + stubGetItem.withArgs('adk_dpt_analytics').returns(JSON.stringify(CAMPAIGN)); + stubSetItem.returns(undefined); + let source = getUmtSource('http://example.com', 'https://www.google.com/search?q=pikachu'); + expect(source).to.be.eql(CAMPAIGN); + }); + + it('should parse referral visit from same domain as direct', () => { + 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', () => { + 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', () => { + let timer; + before(() => { + timer = sandbox.useFakeTimers(0); + }); + after(() => { + 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: 'adapter', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: 'adapter', + 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: 'adapter', + 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: 'adapter', + adUnitCode: 'container-1', + timeToRespond: 443, + size: '300x250' + }; + + describe('Analytics adapter', () => { + let ajaxStub; + let timer; + + before(() => { + ajaxStub = sandbox.stub(ajax, 'ajax'); + timer = sandbox.useFakeTimers(0); + }); + + beforeEach(() => { + sandbox.stub(events, 'getEvents').callsFake(() => { + return [] + }); + }); + + afterEach(() => { + events.getEvents.restore(); + }); + + it('should be configurable', () => { + adaptermanager.registerAnalyticsAdapter({ + code: 'adkernelAdn', + adapter: analyticsAdapter + }); + + adaptermanager.enableAnalytics({ + provider: 'adkernelAdn', + options: { + pubId: 777, + queueTimeout: 1000 + } + }); + + expect(analyticsAdapter.context).to.have.property('host', 'tag.adkernel.com'); + expect(analyticsAdapter.context).to.have.property('pubId', 777); + }); + + it('should handle auction init event', () => { + 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'}); + }); + + it('should handle bid request event', () => { + 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({event: 'bidRequested', adapter: 'adapter', tagid: 'container-1'}); + }); + + it('should handle bid response event', () => { + 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({ + event: 'bidResponse', + adapter: 'adapter', + tagid: 'container-1', + val: 0.015, + time: 0.443 + }); + }); + + it('should handle auction end event', () => { + 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); + ev = JSON.parse(ajaxStub.firstCall.args[2]).hb_ev; + expect(ev[3]).to.be.eql({event: 'auctionEnd', time: 0.447}); + }); + + it('should handle winning bid', () => { + 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; + 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 new file mode 100644 index 00000000000..a7bd959ee8e --- /dev/null +++ b/test/spec/modules/adkernelAdnBidAdapter_spec.js @@ -0,0 +1,269 @@ +import {expect} from 'chai'; +import {spec} from 'modules/adkernelAdnBidAdapter'; +import * as utils from 'src/utils'; + +describe('AdkernelAdn adapter', () => { + const bid1_pub1 = { + bidder: 'adkernelAdn', + transactionId: 'transact0', + bidderRequestId: 'req0', + bidId: 'bidid_1', + params: { + pubId: 1 + }, + adUnitCode: 'ad-unit-1', + sizes: [[300, 250], [300, 200]] + }, + bid2_pub1 = { + bidder: 'adkernelAdn', + transactionId: 'transact1', + bidderRequestId: 'req1', + bidId: 'bidid_2', + params: { + pubId: 1 + }, + adUnitCode: 'ad-unit-2', + sizes: [300, 250] + }, + bid1_pub2 = { + bidder: 'adkernelAdn', + transactionId: 'transact2', + bidderRequestId: 'req1', + bidId: 'bidid_3', + params: { + pubId: 7, + host: 'dps-test.com' + }, + adUnitCode: 'ad-unit-2', + sizes: [[728, 90]] + }, bid_video1 = { + bidder: 'adkernelAdn', + transactionId: 'transact3', + bidderRequestId: 'req1', + bidId: 'bidid_4', + mediaType: 'video', + sizes: [640, 300], + adUnitCode: 'video_wrapper', + params: { + pubId: 7, + video: { + mimes: ['video/mp4', 'video/webm', 'video/x-flv'], + api: [1, 2, 3, 4], + protocols: [1, 2, 3, 4, 5, 6] + } + } + }, bid_video2 = { + bidder: 'adkernelAdn', + transactionId: 'transact3', + bidderRequestId: 'req1', + bidId: 'bidid_5', + mediaTypes: { + video: { + playerSize: [1920, 1080], + 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] + } + } + }; + + const response = { + tags: [{ + id: 'ad-unit-1', + impid: '2c5e951baeeadd', + crid: '108_159810', + bid: 5.0, + tag: '', + w: 300, + h: 250 + }, { + id: 'ad-unit-2', + impid: '31d798477126c4', + crid: '108_21226', + bid: 2.5, + tag: '', + w: 300, + h: 250 + }, { + id: 'video_wrapper', + impid: '57d602ad1c9545', + crid: '108_158802', + bid: 10.0, + vast_url: 'http://vast.com/vast.xml' + }], + syncpages: ['https://dsp.adkernel.com/sync'] + }, usersyncOnlyResponse = { + syncpages: ['https://dsp.adkernel.com/sync'] + }; + + 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', () => { + expect(spec.isBidRequestValid({ + bidder: 'adkernelAdn', + params: {}, + placementCode: 'ad-unit-0', + sizes: [[300, 250]] + })).to.be.equal(false); + }); + it('request with invalid pubid should be ignored', () => { + expect(spec.isBidRequestValid({ + bidder: 'adkernelAdn', + params: { + pubId: 'invalid id' + }, + placementCode: 'ad-unit-0', + sizes: [[300, 250]] + })).to.be.equal(false); + }); + }); + + describe('banner request building', () => { + let pbRequest; + let tagRequest; + + before(() => { + 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' + }; + }); + pbRequest = spec.buildRequests([bid1_pub1])[0]; + tagRequest = JSON.parse(pbRequest.data); + mock.restore(); + }); + + it('should have request id', () => { + expect(tagRequest).to.have.property('id'); + }); + it('should have transaction id', () => { + expect(tagRequest).to.have.property('tid'); + }); + it('should have sizes', () => { + 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', () => { + expect(tagRequest.imp[0]).to.have.property('id', 'bidid_1'); + }); + it('should have tagid', () => { + expect(tagRequest.imp[0]).to.have.property('tagid', 'ad-unit-1'); + }); + it('should create proper site block', () => { + expect(tagRequest.site).to.have.property('page', 'https://example.com/index.html'); + expect(tagRequest.site).to.have.property('secure', 1); + }); + }); + + describe('video request building', () => { + let pbRequest = spec.buildRequests([bid_video1, bid_video2])[0]; + let tagRequest = JSON.parse(pbRequest.data); + + 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', () => { + expect(tagRequest.imp[0]).to.have.property('tagid', 'video_wrapper'); + expect(tagRequest.imp[1]).to.have.property('tagid', 'video_wrapper2'); + }); + 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); + }); + }); + + describe('requests routing', () => { + it('should issue a request for each publisher', () => { + let pbRequests = spec.buildRequests([bid1_pub1, bid_video1]); + expect(pbRequests).to.have.length(2); + expect(pbRequests[0].url).to.have.string(`account=${bid1_pub1.params.pubId}`); + expect(pbRequests[1].url).to.have.string(`account=${bid1_pub2.params.pubId}`); + let tagRequest1 = JSON.parse(pbRequests[0].data); + let tagRequest2 = JSON.parse(pbRequests[1].data); + expect(tagRequest1.imp).to.have.length(1); + expect(tagRequest2.imp).to.have.length(1); + }); + it('should issue a request for each host', () => { + let pbRequests = spec.buildRequests([bid1_pub1, bid1_pub2]); + expect(pbRequests).to.have.length(2); + expect(pbRequests[0].url).to.have.string('//tag.adkernel.com/tag'); + expect(pbRequests[1].url).to.have.string(`//${bid1_pub2.params.host}/tag`); + let tagRequest1 = JSON.parse(pbRequests[0].data); + let tagRequest2 = JSON.parse(pbRequests[1].data); + expect(tagRequest1.imp).to.have.length(1); + expect(tagRequest2.imp).to.have.length(1); + }); + }); + + describe('responses processing', () => { + let responses; + before(() => { + responses = spec.interpretResponse({body: response}); + }); + it('should parse all responses', () => { + expect(responses).to.have.length(3); + }); + it('should return fully-initialized bid-response', () => { + let resp = responses[0]; + expect(resp).to.have.property('bidderCode', 'adkernelAdn'); + expect(resp).to.have.property('requestId', '2c5e951baeeadd'); + expect(resp).to.have.property('cpm', 5.0); + expect(resp).to.have.property('width', 300); + expect(resp).to.have.property('height', 250); + expect(resp).to.have.property('creativeId', '108_159810'); + expect(resp).to.have.property('currency'); + expect(resp).to.have.property('ttl'); + expect(resp).to.have.property('mediaType', 'banner'); + expect(resp).to.have.property('ad'); + expect(resp.ad).to.have.string(''); + }); + it('should return fully-initialized video bid-response', () => { + let resp = responses[2]; + expect(resp).to.have.property('bidderCode', 'adkernelAdn'); + expect(resp).to.have.property('requestId', '57d602ad1c9545'); + expect(resp).to.have.property('cpm', 10.0); + expect(resp).to.have.property('creativeId', '108_158802'); + expect(resp).to.have.property('currency'); + expect(resp).to.have.property('ttl'); + expect(resp).to.have.property('mediaType', 'video'); + expect(resp).to.have.property('vastUrl', 'http://vast.com/vast.xml'); + expect(resp).to.not.have.property('ad'); + }); + it('should perform usersync', () => { + let syncs = spec.getUserSyncs({iframeEnabled: false}, [{body: response}]); + expect(syncs).to.have.length(0); + syncs = spec.getUserSyncs({iframeEnabled: true}, [{body: response}]); + expect(syncs).to.have.length(1); + 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', () => { + let request = spec.buildRequests([bid1_pub1])[0]; + let resp = spec.interpretResponse({body: usersyncOnlyResponse}, request); + expect(resp).to.have.length(0); + }); + it('shouldn\' fail in empty response', () => { + let syncs = spec.getUserSyncs({iframeEnabled: true}, [{body: ''}]); + expect(syncs).to.have.length(0); + }); + }); +}); diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 5fabbad7fbf..cef084c7345 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -1,40 +1,43 @@ import {expect} from 'chai'; -import Adapter from 'modules/adkernelBidAdapter'; -import * as ajax from 'src/ajax'; +import {spec} from 'modules/adkernelBidAdapter'; import * as utils from 'src/utils'; -import bidmanager from 'src/bidmanager'; -import CONSTANTS from 'src/constants.json'; describe('Adkernel adapter', () => { const bid1_zone1 = { bidder: 'adkernel', bidId: 'Bid_01', params: {zoneId: 1, host: 'rtb.adkernel.com'}, - placementCode: 'ad-unit-1', - sizes: [[300, 250]] + adUnitCode: 'ad-unit-1', + sizes: [[300, 250], [300, 200]] }, bid2_zone2 = { bidder: 'adkernel', bidId: 'Bid_02', params: {zoneId: 2, host: 'rtb.adkernel.com'}, - placementCode: 'ad-unit-2', + adUnitCode: 'ad-unit-2', sizes: [[728, 90]] }, bid3_host2 = { bidder: 'adkernel', bidId: 'Bid_02', params: {zoneId: 1, host: 'rtb-private.adkernel.com'}, - placementCode: 'ad-unit-2', + adUnitCode: 'ad-unit-2', sizes: [[728, 90]] }, bid_without_zone = { bidder: 'adkernel', bidId: 'Bid_W', params: {host: 'rtb-private.adkernel.com'}, - placementCode: 'ad-unit-1', + adUnitCode: 'ad-unit-1', sizes: [[728, 90]] }, bid_without_host = { bidder: 'adkernel', bidId: 'Bid_W', params: {zoneId: 1}, - placementCode: 'ad-unit-1', + adUnitCode: 'ad-unit-1', + 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]] }, bid_video = { bidder: 'adkernel', @@ -48,7 +51,7 @@ describe('Adkernel adapter', () => { mimes: ['video/mp4', 'video/webm', 'video/x-flv'] } }, - placementCode: 'ad-unit-1' + adUnitCode: 'ad-unit-1' }; const bidResponse1 = { @@ -57,20 +60,29 @@ describe('Adkernel adapter', () => { bid: [{ id: '1', impid: 'Bid_01', + crid: '100_001', price: 3.01, nurl: 'https://rtb.com/win?i=ZjKoPYSFI3Y_0', - adm: '' + adm: '', + w: 300, + h: 250 }] }], - cur: 'USD' + cur: 'USD', + ext: { + adk_usersync: ['http://adk.sync.com/sync'] + } }, bidResponse2 = { id: 'bid2', seatbid: [{ bid: [{ id: '2', impid: 'Bid_02', + crid: '100_002', price: 1.31, - adm: '' + adm: '', + w: 300, + h: 250 }] }], cur: 'USD' @@ -80,85 +92,56 @@ describe('Adkernel adapter', () => { bid: [{ id: 'sZSYq5zYMxo_0', impid: 'Bid_Video', + crid: '100_003', price: 0.00145, adid: '158801', nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl', - cid: '16855', - crid: '158801', - w: 600, - h: 400 + cid: '16855' }] }], cur: 'USD' + }, usersyncOnlyResponse = { + id: 'nobid1', + ext: { + adk_usersync: ['http://adk.sync.com/sync'] + } }; - let adapter, - sandbox, - ajaxStub; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - adapter = new Adapter(); - ajaxStub = sandbox.stub(ajax, 'ajax'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - function doRequest(bids) { - adapter.callBids({ - bidderCode: 'adkernel', - bids: bids - }); - } - describe('input parameters validation', () => { - let spy; - - beforeEach(() => { - spy = sandbox.spy(); - sandbox.stub(bidmanager, 'addBidResponse'); - }); - it('empty request shouldn\'t generate exception', () => { - expect(adapter.callBids({ + expect(spec.isBidRequestValid({ bidderCode: 'adkernel' - })).to.be.an('undefined'); + })).to.be.equal(false); }); it('request without zone shouldn\'t issue a request', () => { - doRequest([bid_without_zone]); - sinon.assert.notCalled(ajaxStub); - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(bidmanager.addBidResponse.firstCall.args[1].bidderCode).to.equal('adkernel'); + expect(spec.isBidRequestValid(bid_without_zone)).to.be.equal(false); }); it('request without host shouldn\'t issue a request', () => { - doRequest([bid_without_host]); - sinon.assert.notCalled(ajaxStub); - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(bidmanager.addBidResponse.firstCall.args[1].bidderCode).to.equal('adkernel'); + expect(spec.isBidRequestValid(bid_without_host)).to.be.equal(false); + }); + + it('empty request shouldn\'t generate exception', () => { + expect(spec.isBidRequestValid(bid_with_wrong_zoneId)).to.be.equal(false); }); }); describe('banner request building', () => { let bidRequest; - - beforeEach(() => { - sandbox.stub(utils, 'getTopWindowLocation', () => { - return { - protocol: 'https:', - hostname: 'example.com', - host: 'example.com', - pathname: '/index.html', - href: 'http://example.com/index.html' - }; - }); - - ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(bidResponse1)); - doRequest([bid1_zone1]); - bidRequest = JSON.parse(decodeURIComponent(ajaxStub.getCall(0).args[2].r)); + before(() => { + let wmock = sinon.stub(utils, 'getTopWindowLocation').callsFake(() => ({ + protocol: 'https:', + hostname: 'example.com', + host: 'example.com', + pathname: '/index.html', + href: 'https://example.com/index.html' + })); + let dntmock = sinon.stub(utils, 'getDNT').callsFake(() => true); + let request = spec.buildRequests([bid1_zone1])[0]; + bidRequest = JSON.parse(request.data.r); + wmock.restore(); + dntmock.restore(); }); it('should be a first-price auction', () => { @@ -169,9 +152,9 @@ describe('Adkernel adapter', () => { expect(bidRequest.imp[0]).to.have.property('banner'); }); - it('should have h/w', () => { - expect(bidRequest.imp[0].banner).to.have.property('w', 300); - expect(bidRequest.imp[0].banner).to.have.property('h', 250); + it('should have w/h', () => { + 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}]); }); it('should respect secure connection', () => { @@ -184,32 +167,23 @@ describe('Adkernel adapter', () => { it('should create proper site block', () => { expect(bidRequest.site).to.have.property('domain', 'example.com'); - expect(bidRequest.site).to.have.property('page', 'http://example.com/index.html'); + expect(bidRequest.site).to.have.property('page', 'https://example.com/index.html'); }); it('should fill device with caller macro', () => { expect(bidRequest).to.have.property('device'); expect(bidRequest.device).to.have.property('ip', 'caller'); expect(bidRequest.device).to.have.property('ua', 'caller'); - }) + expect(bidRequest.device).to.have.property('dnt', 1); + }); }); describe('video request building', () => { let bidRequest; - beforeEach(() => { - sandbox.stub(utils, 'getTopWindowLocation', () => { - return { - protocol: 'https:', - hostname: 'example.com', - host: 'example.com', - pathname: '/index.html', - href: 'http://example.com/index.html' - }; - }); - ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(videoBidResponse)); - doRequest([bid_video]); - bidRequest = JSON.parse(decodeURIComponent(ajaxStub.getCall(0).args[2].r)); + before(() => { + let request = spec.buildRequests([bid_video])[0]; + bidRequest = JSON.parse(request.data.r); }); it('should have video object', () => { @@ -227,123 +201,68 @@ describe('Adkernel adapter', () => { }); describe('requests routing', () => { - it('should issue a request for each network', () => { - ajaxStub.onFirstCall().callsArgWith(1, '') - .onSecondCall().callsArgWith(1, ''); - doRequest([bid1_zone1, bid3_host2]); - expect(ajaxStub.calledTwice); - expect(ajaxStub.firstCall.args[0]).to.include(bid1_zone1.params.host); - expect(ajaxStub.secondCall.args[0]).to.include(bid3_host2.params.host); + it('should issue a request for each host', () => { + let pbRequests = spec.buildRequests([bid1_zone1, bid3_host2]); + expect(pbRequests).to.have.length(2); + expect(pbRequests[0].url).to.have.string(`//${bid1_zone1.params.host}/`); + expect(pbRequests[1].url).to.have.string(`//${bid3_host2.params.host}/`); }); it('should issue a request for each zone', () => { - ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(bidResponse1)); - ajaxStub.onCall(1).callsArgWith(1, JSON.stringify(bidResponse2)); - doRequest([bid1_zone1, bid2_zone2]); - expect(ajaxStub.calledTwice); - }); - - it('should route calls to proper zones', () => { - ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(bidResponse1)); - ajaxStub.onCall(1).callsArgWith(1, JSON.stringify(bidResponse2)); - doRequest([bid1_zone1, bid2_zone2]); - expect(ajaxStub.firstCall.args[2].zone).to.equal('1'); - expect(ajaxStub.secondCall.args[2].zone).to.equal('2'); + let pbRequests = spec.buildRequests([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); }); }); describe('responses processing', () => { - beforeEach(() => { - sandbox.stub(bidmanager, 'addBidResponse'); - }); - - it('should return fully-initialized bid-response', () => { - ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(bidResponse1)); - doRequest([bid1_zone1]); - let bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('ad-unit-1'); - expect(bidResponse.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidResponse.bidderCode).to.equal('adkernel'); - expect(bidResponse.cpm).to.equal(3.01); - expect(bidResponse.ad).to.include(''); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); + it('should return fully-initialized banner bid-response', () => { + let request = spec.buildRequests([bid1_zone1])[0]; + let resp = spec.interpretResponse({body: bidResponse1}, request)[0]; + expect(resp).to.have.property('requestId', 'Bid_01'); + expect(resp).to.have.property('cpm', 3.01); + expect(resp).to.have.property('width', 300); + expect(resp).to.have.property('height', 250); + expect(resp).to.have.property('creativeId', '100_001'); + expect(resp).to.have.property('currency'); + expect(resp).to.have.property('ttl'); + expect(resp).to.have.property('mediaType', 'banner'); + expect(resp).to.have.property('ad'); + expect(resp.ad).to.have.string(''); }); it('should return fully-initialized video bid-response', () => { - ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(videoBidResponse)); - doRequest([bid_video]); - let bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('ad-unit-1'); - expect(bidResponse.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidResponse.mediaType).to.equal('video'); - expect(bidResponse.bidderCode).to.equal('adkernel'); - expect(bidResponse.cpm).to.equal(0.00145); - expect(bidResponse.vastUrl).to.equal('https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl'); - expect(bidResponse.width).to.equal(600); - expect(bidResponse.height).to.equal(400); - }); - - it('should map responses to proper ad units', () => { - ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(bidResponse1)); - ajaxStub.onCall(1).callsArgWith(1, JSON.stringify(bidResponse2)); - doRequest([bid1_zone1, bid2_zone2]); - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidmanager.addBidResponse.firstCall.args[1].bidderCode).to.equal('adkernel'); - expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('ad-unit-1'); - expect(bidmanager.addBidResponse.secondCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidmanager.addBidResponse.secondCall.args[1].bidderCode).to.equal('adkernel'); - expect(bidmanager.addBidResponse.secondCall.args[0]).to.equal('ad-unit-2'); - }); - - it('should process empty responses', () => { - ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(bidResponse1)); - ajaxStub.onCall(1).callsArgWith(1, ''); - doRequest([bid1_zone1, bid2_zone2]); - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidmanager.addBidResponse.firstCall.args[1].bidderCode).to.equal('adkernel'); - expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('ad-unit-1'); - expect(bidmanager.addBidResponse.secondCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(bidmanager.addBidResponse.secondCall.args[1].bidderCode).to.equal('adkernel'); - expect(bidmanager.addBidResponse.secondCall.args[0]).to.equal('ad-unit-2'); + let request = spec.buildRequests([bid_video])[0]; + let resp = spec.interpretResponse({body: videoBidResponse}, request)[0]; + expect(resp).to.have.property('requestId', 'Bid_Video'); + expect(resp.mediaType).to.equal('video'); + expect(resp.cpm).to.equal(0.00145); + expect(resp.vastUrl).to.equal('https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl'); + expect(resp.width).to.equal(640); + expect(resp.height).to.equal(480); }); it('should add nurl as pixel for banner response', () => { - sandbox.spy(utils, 'createTrackPixelHtml'); - ajaxStub.onCall(0).callsArgWith(1, JSON.stringify(bidResponse1)); - doRequest([bid1_zone1]); - expect(utils.createTrackPixelHtml.calledOnce); - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + let request = spec.buildRequests([bid1_zone1])[0]; + let resp = spec.interpretResponse({body: bidResponse1}, request)[0]; let expectedNurl = bidResponse1.seatbid[0].bid[0].nurl + '&px=1'; - expect(bidmanager.addBidResponse.firstCall.args[1].ad).to.include(expectedNurl); + expect(resp.ad).to.have.string(expectedNurl); }); - it('should perform usersync for each unique host/zone combination', () => { - ajaxStub.callsArgWith(1, ''); - const expectedSyncUrls = ['//sync.adkernel.com/user-sync?zone=1&r=%2F%2Frtb-private.adkernel.com%2Fuser-synced%3Fuid%3D%7BUID%7D', - '//sync.adkernel.com/user-sync?zone=2&r=%2F%2Frtb.adkernel.com%2Fuser-synced%3Fuid%3D%7BUID%7D', - '//sync.adkernel.com/user-sync?zone=1&r=%2F%2Frtb.adkernel.com%2Fuser-synced%3Fuid%3D%7BUID%7D']; - let userSyncUrls = []; - sandbox.stub(utils, 'createInvisibleIframe', () => { - return {}; - }); - sandbox.stub(utils, 'addEventHandler', (el, ev, cb) => { - userSyncUrls.push(el.src); - cb(); // instant callback - }); - doRequest([bid1_zone1, bid2_zone2, bid2_zone2, bid3_host2]); - expect(utils.createInvisibleIframe.calledThrice); - expect(userSyncUrls).to.be.eql(expectedSyncUrls); + it('should handle bidresponse with user-sync only', () => { + let request = spec.buildRequests([bid1_zone1])[0]; + let resp = spec.interpretResponse({body: usersyncOnlyResponse}, request); + expect(resp).to.have.length(0); }); - }); - - describe('adapter aliasing', () => { - const ALIAS_NAME = 'adkernelAlias'; - it('should allow bidder code changing', () => { - expect(adapter.getBidderCode()).to.equal('adkernel'); - adapter.setBidderCode(ALIAS_NAME); - expect(adapter.getBidderCode()).to.equal(ALIAS_NAME); + it('should perform usersync', () => { + let syncs = spec.getUserSyncs({iframeEnabled: false}, [{body: bidResponse1}]); + expect(syncs).to.have.length(0); + syncs = spec.getUserSyncs({iframeEnabled: true}, [{body: bidResponse1}]); + expect(syncs).to.have.length(1); + expect(syncs[0]).to.have.property('type', 'iframe'); + expect(syncs[0]).to.have.property('url', 'http://adk.sync.com/sync'); }); }); }); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 0b66f8a9469..13312e3d24e 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -1,244 +1,117 @@ -var chai = require('chai'); -var Adapter = require('modules/admixerBidAdapter')(); -var Ajax = require('src/ajax'); -var bidmanager = require('src/bidmanager.js'); -var CONSTANTS = require('src/constants.json'); +import {expect} from 'chai'; +import {spec} from 'modules/admixerBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; -describe('Admixer adapter', function () { - var validData_1 = { - bids: [ - { - bidder: 'admixer', - bidId: 'bid_id', - params: {zone: 'zone_id'}, - placementCode: 'ad-unit-1', - sizes: [[300, 250], [300, 600]] - } - ] - }; - var validData_2 = { - bids: [ - { - bidder: 'admixer', - bidId: 'bid_id', - params: {zone: 'zone_id'}, - placementCode: 'ad-unit-1', - sizes: [300, 250] - } - ] - }; - var invalidData = { - bids: [ - { - bidder: 'admixer', - bidId: 'bid_id', - params: {}, - placementCode: 'ad-unit-1', - sizes: [[300, 250], [300, 600]] - } - ] - }; - var validVideoData_1 = { - bids: [ - { - mediaType: 'video', - bidder: 'admixer', - bidId: 'bid_id', - params: {zone: 'zone_id'}, - placementCode: 'ad-unit-1', - sizes: [[300, 250], [300, 600]] - } - ] - }; - var validVideoData_2 = { - bids: [ - { - mediaType: 'video', - bidder: 'admixer', - bidId: 'bid_id', - params: {zone: 'zone_id'}, - placementCode: 'ad-unit-1', - sizes: [300, 250] - } - ] - }; - var validVideoData_3 = { - bids: [ - { - mediaType: 'video', - bidder: 'admixer', - bidId: 'bid_id', - params: {zone: 'zone_id', video: {skippable: true}}, - placementCode: 'ad-unit-1', - sizes: [300, 250] - } - ] - }; - var invalidVideoData = { - bids: [ - { - mediaType: 'video', - bidder: 'admixer', - bidId: 'bid_id', - params: {}, - placementCode: 'ad-unit-1', - sizes: [[300, 250], [300, 600]] - } - ] - }; - var responseWithAd = JSON.stringify({ - 'result': { - 'cpm': 2.2, - 'ad': '
response ad
', - 'width': 300, - 'height': 250 - }, - 'callback_uid': 'ad-unit-1' - }); - var responseWithoutAd = JSON.stringify({ - 'result': { - 'cpm': 0, - 'ad': '', - 'width': 0, - 'height': 0 - }, - 'callback_uid': 'ad-unit-1' - }); - var responseWithVideoAd = JSON.stringify({ - 'result': { - 'cpm': 2.2, - 'vastUrl': 'http://inv-nets.admixer.net/vastxml.aspx?req=9d651544-daf4-48ed-ae0c-38a60a4e1920&vk=e914f026449e49aeb6eea07b9642a2ce', - 'width': 300, - 'height': 250 - }, - 'callback_uid': 'ad-unit-1' - }); - var responseWithoutVideoAd = JSON.stringify({ - 'result': { - 'cpm': 0, - 'vastUrl': '', - 'width': 0, - 'height': 0 - }, - 'callback_uid': 'ad-unit-1' - }); - var responseEmpty = ''; - var invUrl = '//inv-nets.admixer.net/prebid.aspx'; - var invVastUrl = '//inv-nets.admixer.net/videoprebid.aspx'; - var validJsonParams = { - zone: 'zone_id', - callback_uid: 'ad-unit-1', - sizes: '300x250-300x600' - }; - var validJsonVideoParams = { - zone: 'zone_id', - callback_uid: 'ad-unit-1', - sizes: '300x250-300x600', - skippable: true - }; - describe('bid request with valid data', function () { - var stubAjax; - beforeEach(function () { - stubAjax = sinon.stub(Ajax, 'ajax'); - }); +const BIDDER_CODE = 'admixer'; +const ENDPOINT_URL = '//inv-nets.admixer.net/prebid.1.0.aspx'; +const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; - afterEach(function () { - stubAjax.restore(); - }); - it('display: bid request should be called. sizes style -> [[],[]]', function () { - Adapter.callBids(validData_1); - sinon.assert.calledOnce(stubAjax); - }); - it('video: bid request should be called. sizes style -> [[],[]]', function () { - Adapter.callBids(validVideoData_1); - sinon.assert.calledOnce(stubAjax); - }); - it('display: bid request should be called. sizes style -> []', function () { - Adapter.callBids(validData_2); - sinon.assert.calledOnce(stubAjax); - }); - it('video: bid request should be called. sizes style -> []', function () { - Adapter.callBids(validVideoData_2); - sinon.assert.calledOnce(stubAjax); - }); - it('display: ajax params should be matched', function () { - Adapter.callBids(validData_1); - sinon.assert.calledWith(stubAjax, sinon.match(invUrl, function () { - }, validJsonParams, {method: 'GET'})); - }); - it('video: ajax params should be matched', function () { - Adapter.callBids(validVideoData_3); - sinon.assert.calledWith(stubAjax, sinon.match(invVastUrl, function () { - }, validJsonVideoParams, {method: 'GET'})); +describe('AdmixerAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.be.exist.and.to.be.a('function'); }); }); - describe('bid request with invalid data', function () { - var addBidResponse, stubAjax; - beforeEach(function () { - addBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - stubAjax = sinon.stub(Ajax, 'ajax'); - }); - afterEach(function () { - addBidResponse.restore(); - stubAjax.restore(); - }); - it('display: ajax shouldn\'t be called', function () { - Adapter.callBids(invalidData); - sinon.assert.notCalled(stubAjax); - }); - it('video: ajax shouldn\'t be called', function () { - Adapter.callBids(invalidVideoData); - sinon.assert.notCalled(stubAjax); - }); - it('display: bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID + '"', function () { - Adapter.callBids(invalidData); - expect(addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(addBidResponse.firstCall.args[1].bidderCode).to.equal('admixer'); + describe('isBidRequestValid', () => { + 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', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('video: bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID + '"', function () { - Adapter.callBids(invalidVideoData); - expect(addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(addBidResponse.firstCall.args[1].bidderCode).to.equal('admixer'); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); - describe('bid response', function () { - var addBidResponse; - beforeEach(function () { - addBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - }); - afterEach(function () { - addBidResponse.restore(); - }); - it('display: response with ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.GOOD + '"', function () { - Adapter.responseCallback(responseWithAd); - var arg = addBidResponse.firstCall.args[1]; - expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(arg.bidderCode).to.equal('admixer'); - }); - it('video: response with ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.GOOD + '"', function () { - Adapter.responseCallback(responseWithVideoAd); - var arg = addBidResponse.firstCall.args[1]; - expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(arg.bidderCode).to.equal('admixer'); + + describe('buildRequests', () => { + 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', () => { + 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('display: response without ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID, function () { - Adapter.responseCallback(responseWithoutAd); - var arg = addBidResponse.firstCall.args[1]; - expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(arg.bidderCode).to.equal('admixer'); + + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT_URL); + expect(request.method).to.equal('GET'); }); - it('video: response without ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID, function () { - Adapter.responseCallback(responseWithoutVideoAd); - var arg = addBidResponse.firstCall.args[1]; - expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(arg.bidderCode).to.equal('admixer'); + }); + + describe('interpretResponse', () => { + 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', () => { + 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('display/video: response empty. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID, function () { - Adapter.responseCallback(responseEmpty); - var arg = addBidResponse.firstCall.args[1]; - expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(arg.bidderCode).to.equal('admixer'); + + it('handles nobid responses', () => { + let response = []; + + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); }); }); }); diff --git a/test/spec/modules/adoceanBidAdapter_spec.js b/test/spec/modules/adoceanBidAdapter_spec.js new file mode 100644 index 00000000000..149b9eb4d53 --- /dev/null +++ b/test/spec/modules/adoceanBidAdapter_spec.js @@ -0,0 +1,158 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adoceanBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('AdoceanAdapter', () => { + 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', () => { + const bid = { + 'bidder': 'adocean', + 'params': { + 'masterId': 'tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7', + 'slaveId': 'adoceanmyaozpniqismex', + 'emiter': 'myao.adocean.pl' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + 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', () => { + const bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'masterId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const bidRequests = [ + { + 'bidder': 'adocean', + 'params': { + 'masterId': 'tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7', + 'slaveId': 'adoceanmyaozpniqismex', + 'emiter': 'myao.adocean.pl' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should add bidIdMap with slaveId => bidId mapping', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.bidIdMap).to.exists; + const bidIdMap = request.bidIdMap; + expect(bidIdMap[bidRequests[0].params.slaveId]).to.equal(bidRequests[0].bidId); + }); + + it('sends bid request to url via GET', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.method).to.equal('GET'); + expect(request.url).to.match(new RegExp(`^https://${bidRequests[0].params.emiter}/ad.json`)); + }); + + it('should attach id to url', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.include('id=' + bidRequests[0].params.masterId); + }); + }) + + describe('interpretResponse', () => { + const response = { + 'body': [ + { + 'id': 'adoceanmyaozpniqismex', + 'price': '0.019000', + 'winurl': '', + 'statsUrl': '', + 'code': '%3C!--%20Creative%20--%3E', + 'currency': 'EUR', + 'minFloorPrice': '0.01', + 'width': '300', + 'height': '250', + 'crid': '0af345b42983cc4bc0', + 'ttl': '300' + } + ], + 'headers': { + 'get': function() {} + } + }; + + const bidRequest = { + 'bidder': 'adocean', + 'params': { + 'masterId': 'tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7', + 'slaveId': 'adoceanmyaozpniqismex', + 'emiter': 'myao.adocean.pl' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidIdMap': { + 'adoceanmyaozpniqismex': '30b31c1838de1e' + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should get correct bid response', () => { + const expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'cpm': 0.019000, + 'currency': 'EUR', + 'width': 300, + 'height': 250, + 'ad': '', + 'creativeId': '0af345b42983cc4bc0', + 'ttl': 300, + 'netRevenue': false + } + ]; + + const result = spec.interpretResponse(response, bidRequest); + expect(result).to.have.lengthOf(1); + let resultKeys = Object.keys(result[0]); + expect(resultKeys.sort()).to.deep.equal(Object.keys(expectedResponse[0]).sort()); + resultKeys.forEach(function(k) { + if (k === 'ad') { + expect(result[0][k]).to.match(/$/); + } else { + expect(result[0][k]).to.equal(expectedResponse[0][k]); + } + }); + }); + + it('handles nobid responses', () => { + response.body = [ + { + 'id': 'adoceanmyaolafpjwftbz', + 'error': 'true' + } + ]; + + const result = spec.interpretResponse(response, bidRequest); + expect(result).to.have.lengthOf(0); + }); + }); +}); diff --git a/test/spec/modules/adomikAnalyticsAdapter_spec.js b/test/spec/modules/adomikAnalyticsAdapter_spec.js index a3e0d214e5a..59e6dadec96 100644 --- a/test/spec/modules/adomikAnalyticsAdapter_spec.js +++ b/test/spec/modules/adomikAnalyticsAdapter_spec.js @@ -5,15 +5,26 @@ let adaptermanager = require('src/adaptermanager'); let constants = require('src/constants.json'); describe('Adomik Prebid Analytic', function () { + let sendEventStub; + let sendWonEventStub; + describe('enableAnalytics', function () { beforeEach(() => { sinon.spy(adomikAnalytics, 'track'); - sinon.spy(adomikAnalytics, 'sendTypedEvent'); + sendEventStub = sinon.stub(adomikAnalytics, 'sendTypedEvent'); + sendWonEventStub = sinon.stub(adomikAnalytics, 'sendWonEvent'); + sinon.stub(events, 'getEvents').returns([]); }); afterEach(() => { adomikAnalytics.track.restore(); - adomikAnalytics.sendTypedEvent.restore(); + sendEventStub.restore(); + sendWonEventStub.restore(); + events.getEvents.restore(); + }); + + after(() => { + adomikAnalytics.disableAnalytics(); }); it('should catch all events', function (done) { @@ -33,7 +44,7 @@ describe('Adomik Prebid Analytic', function () { height: 10, statusMessage: 'Bid available', adId: '1234', - requestId: '', + auctionId: '', responseTimestamp: 1496410856397, requestTimestamp: 1496410856295, cpm: 0.1, @@ -51,25 +62,21 @@ describe('Adomik Prebid Analytic', function () { expect(adomikAnalytics.currentContext).to.deep.equal({ uid: '123456', url: 'testurl', - debug: undefined, id: '', - timeouted: false, - timeout: 0, + timeouted: false }); - // Step 1: Send init auction event - events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, requestId: 'test-test-test', timeout: 3000}); + // Step 2: Send init auction event + events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, auctionId: 'test-test-test'}); expect(adomikAnalytics.currentContext).to.deep.equal({ uid: '123456', url: 'testurl', - debug: undefined, id: 'test-test-test', - timeouted: false, - timeout: 3000, + timeouted: false }); - // Step 2: Send bid requested event + // Step 3: Send bid requested event events.emit(constants.EVENTS.BID_REQUESTED, { bids: [bid] }); expect(adomikAnalytics.bucketEvents.length).to.equal(1); @@ -81,7 +88,7 @@ describe('Adomik Prebid Analytic', function () { } }); - // Step 3: Send bid response event + // Step 4: Send bid response event events.emit(constants.EVENTS.BID_RESPONSE, bid); expect(adomikAnalytics.bucketEvents.length).to.equal(2); @@ -102,29 +109,23 @@ describe('Adomik Prebid Analytic', function () { } }); - // Step 4: Send bid won event + // Step 5: Send bid won event events.emit(constants.EVENTS.BID_WON, bid); - expect(adomikAnalytics.bucketEvents.length).to.equal(3); - expect(adomikAnalytics.bucketEvents[2]).to.deep.equal({ - type: 'winner', - event: { - id: '1234', - placementCode: '0000', - } - }); + expect(adomikAnalytics.bucketEvents.length).to.equal(2); - // Step 5: Send bid timeout event + // Step 6: Send bid timeout event events.emit(constants.EVENTS.BID_TIMEOUT, {}); expect(adomikAnalytics.currentContext.timeouted).to.equal(true); - // Step 6: Send auction end event + // Step 7: Send auction end event var clock = sinon.useFakeTimers(); events.emit(constants.EVENTS.AUCTION_END, {}); setTimeout(function() { - sinon.assert.callCount(adomikAnalytics.sendTypedEvent, 1); + sinon.assert.callCount(sendEventStub, 1); + sinon.assert.callCount(sendWonEventStub, 1); done(); }, 3000); diff --git a/test/spec/modules/adsupplyBidAdapter_spec.js b/test/spec/modules/adsupplyBidAdapter_spec.js deleted file mode 100644 index b1bc0bf17f1..00000000000 --- a/test/spec/modules/adsupplyBidAdapter_spec.js +++ /dev/null @@ -1,359 +0,0 @@ -describe('adsupply adapter tests', function () { - const expect = require('chai').expect; - - const AdSupplyAdapter = require('../../../modules/adsupplyBidAdapter'); - const adloader = require('../../../src/adloader'); - const bidmanager = require('../../../src/bidmanager'); - const CONSTANTS = require('../../../src/constants.json'); - let adsupplyAdapter = new AdSupplyAdapter(); - - beforeEach(() => { - $$PREBID_GLOBAL$$._bidsRequested = []; - }); - - it('adsupply response handler should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.adSupplyResponseHandler).to.exist.and.to.be.a('function'); - }); - - it('two requests are sent to adsupply engine', function () { - let stubLoadScript = sinon.stub(adloader, 'loadScript'); - - let request = { - bids: [{ - placementCode: 'pc1', - bidder: 'adsupply', - bidId: 'bidId1', - params: { - zoneId: 111, - clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - endpointUrl: 'engine.4dsply.com' - } - }, - { - placementCode: 'pc2', - bidder: 'adsupply', - bidId: 'bidId2', - params: { - clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', - zoneId: 222, - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - endpointUrl: 'engine.4dsply.com' - } - }] - }; - - adsupplyAdapter.callBids(request); - - sinon.assert.calledTwice(stubLoadScript); - - adloader.loadScript.restore(); - }); - - it('zoneId is not a number and not specified', function () { - let stubLoadScript = sinon.stub(adloader, 'loadScript'); - - let request = { - bids: [{ - placementCode: 'pc1', - bidder: 'adsupply', - bidId: 'bidId1', - params: { - clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', - zoneId: '111', - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - endpointUrl: 'engine.4dsply.com' - } - }, - { - placementCode: 'pc2', - bidder: 'adsupply', - bidId: 'bidId2', - params: { - clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - endpointUrl: 'engine.4dsply.com' - } - }] - }; - - adsupplyAdapter.callBids(request); - - sinon.assert.notCalled(stubLoadScript); - - adloader.loadScript.restore(); - }); - - it('siteId is empty and not specified', function () { - let stubLoadScript = sinon.stub(adloader, 'loadScript'); - - let request = { - bids: [{ - placementCode: 'pc1', - bidder: 'adsupply', - bidId: 'bidId1', - params: { - zoneId: 111, - siteId: '', - clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', - endpointUrl: 'engine.4dsply.com' - } - }, - { - placementCode: 'pc2', - bidder: 'adsupply', - bidId: 'bidId2', - params: { - zoneId: 222, - clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', - endpointUrl: 'engine.4dsply.com' - } - }] - }; - - adsupplyAdapter.callBids(request); - - sinon.assert.notCalled(stubLoadScript); - - adloader.loadScript.restore(); - }); - - it('endpointUrl is empty and not specified', function () { - let stubLoadScript = sinon.stub(adloader, 'loadScript'); - - let request = { - bids: [{ - placementCode: 'pc1', - bidder: 'adsupply', - bidId: 'bidId1', - params: { - clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', - zoneId: 111, - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - endpointUrl: '' - } - }, - { - placementCode: 'pc2', - bidder: 'adsupply', - bidId: 'bidId2', - params: { - clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', - zoneId: 222, - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - } - }] - }; - - adsupplyAdapter.callBids(request); - - sinon.assert.notCalled(stubLoadScript); - - adloader.loadScript.restore(); - }); - - it('clientId is empty and not specified', function () { - let stubLoadScript = sinon.stub(adloader, 'loadScript'); - - let request = { - bids: [{ - placementCode: 'pc1', - bidder: 'adsupply', - bidId: 'bidId1', - params: { - clientId: '', - zoneId: 111, - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - endpointUrl: 'engine.4dsply.com' - } - }, - { - placementCode: 'pc2', - bidder: 'adsupply', - bidId: 'bidId2', - params: { - zoneId: 222, - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - endpointUrl: 'engine.4dsply.com' - } - }] - }; - - adsupplyAdapter.callBids(request); - - sinon.assert.notCalled(stubLoadScript); - - adloader.loadScript.restore(); - }); - - it('parameters are missed', function () { - let stubLoadScript = sinon.stub(adloader, 'loadScript'); - - let request = { - bids: [{ - placementCode: 'pc1', - bidder: 'adsupply', - bidId: 'bidId1' - }] - }; - - adsupplyAdapter.callBids(request); - - sinon.assert.notCalled(stubLoadScript); - - adloader.loadScript.restore(); - }); - - it('Parameters added to the request url', function () { - let stubLoadScript = sinon.stub(adloader, 'loadScript'); - - let request = { - bids: [{ - placementCode: 'pc1', - bidder: 'adsupply', - bidId: 'bidId1', - params: { - zoneId: 111, - clientId: 'g32db6906-55f4-42b1-a7d2-7dfaddce96fd', - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - endpointUrl: 'engine.4dsply.com' - } - }] - }; - - adsupplyAdapter.callBids(request); - - var requestUrl = stubLoadScript.getCall(0).args[0]; - expect(requestUrl).to.contain('111'); - expect(requestUrl).to.contain('0ab16161-a1de-4683-8837-c420bd4387c0'); - expect(requestUrl).to.contain('engine.4dsply.com'); - expect(requestUrl).to.contain('&hbt=1'); - expect(requestUrl).to.contain('g32db6906-55f4-42b1-a7d2-7dfaddce96fd'); - - adloader.loadScript.restore(); - }); - - it('Response handler invalid data', function () { - let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - // adapter needs to be called, in order for the stub to register. - new AdSupplyAdapter(); - - // bidId is not valid - $$PREBID_GLOBAL$$.adSupplyResponseHandler(null); - - // bidRequest object is not found - $$PREBID_GLOBAL$$.adSupplyResponseHandler('bidId1'); - - let clientId = 'g5d384afa-c050-4bac-b202-dab8fb06e381'; - // Zone property is not found - let bidderRequest = { - bidderCode: 'adsupply', - bids: [{ - placementCode: 'pc1', - bidder: 'adsupply', - bidId: 'bidId1', - params: { - clientId: clientId, - zoneId: 111, - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - endpointUrl: 'engine.4dsply.com' - } - }] - }; - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - $$PREBID_GLOBAL$$.adSupplyResponseHandler('bidId1'); - - // Media is not found - window[clientId] = window[clientId] || {}; - window[clientId]['b111'] = window[clientId]['b111'] || {}; - $$PREBID_GLOBAL$$.adSupplyResponseHandler('bidId1'); - - sinon.assert.notCalled(stubAddBidResponse); - - $$PREBID_GLOBAL$$._bidsRequested.pop(); - bidmanager.addBidResponse.restore(); - }); - - it('No Fill response', function () { - let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - // adapter needs to be called, in order for the stub to register. - new AdSupplyAdapter(); - - let clientId = 'g5d384afa-c050-4bac-b202-dab8fb06e381'; - // Zone property is not found - let bidderRequest = { - bidderCode: 'adsupply', - bids: [{ - placementCode: 'pc1', - bidder: 'adsupply', - bidId: 'bidId1', - params: { - clientId: clientId, - zoneId: 111, - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - endpointUrl: 'engine.4dsply.com' - } - }] - }; - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - - window[clientId] = window[clientId] || {}; - window[clientId]['b111'] = window[clientId]['b111'] || {}; - window[clientId]['b111'].Media = { width: 300 }; - $$PREBID_GLOBAL$$.adSupplyResponseHandler('bidId1'); - - sinon.assert.calledOnce(stubAddBidResponse); - - let bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; - let bidResponse = stubAddBidResponse.getCall(0).args[1]; - expect(bidPlacementCode).to.equal('pc1'); - expect(bidResponse.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(bidResponse.bidderCode).to.equal('adsupply'); - - $$PREBID_GLOBAL$$._bidsRequested.pop(); - bidmanager.addBidResponse.restore(); - }); - - it('Fill response', function () { - let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - // adapter needs to be called, in order for the stub to register. - new AdSupplyAdapter(); - - let clientId = 'g5d384afa-c050-4bac-b202-dab8fb06e381'; - // Zone property is not found - let bidderRequest = { - bidderCode: 'adsupply', - bids: [{ - placementCode: 'pc1', - bidder: 'adsupply', - bidId: 'bidId1', - params: { - clientId: clientId, - zoneId: 111, - siteId: '0ab16161-a1de-4683-8837-c420bd4387c0', - endpointUrl: 'engine.4dsply.com' - } - }] - }; - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - - window[clientId] = window[clientId] || {}; - window[clientId]['b111'] = window[clientId]['b111'] || {}; - window[clientId]['b111'].Media = { Width: 300, Height: 250, Url: '/Redirect.engine', Ecpm: 0.0012 }; - $$PREBID_GLOBAL$$.adSupplyResponseHandler('bidId1'); - - sinon.assert.calledOnce(stubAddBidResponse); - - let bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; - let bidResponse = stubAddBidResponse.getCall(0).args[1]; - expect(bidPlacementCode).to.equal('pc1'); - expect(bidResponse.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidResponse.bidderCode).to.equal('adsupply'); - - $$PREBID_GLOBAL$$._bidsRequested.pop(); - bidmanager.addBidResponse.restore(); - }); -}); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js new file mode 100644 index 00000000000..fa3e0479372 --- /dev/null +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -0,0 +1,240 @@ +import {expect} from 'chai'; +import {spec} from 'modules/adtelligentBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +const ENDPOINT = '//hb.adtelligent.com/auction/'; + +const DISPLAY_REQUEST = { + 'bidder': 'adtelligent', + 'params': { + 'aid': 12345 + }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d', + 'sizes': [300, 250] +}; + +const VIDEO_REQUEST = { + 'bidder': 'adtelligent', + 'mediaTypes': { + 'video': {} + }, + 'params': { + 'aid': 12345 + }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d', + 'sizes': [[480, 360], [640, 480]] +}; + +const SERVER_VIDEO_RESPONSE = { + 'source': {'aid': 12345, 'pubId': 54321}, + 'bids': [{ + 'vastUrl': 'http://rtb.adtelligent.com/vast/?adid=44F2AEB9BFC881B3', + 'requestId': '2e41f65424c87c', + 'url': '44F2AEB9BFC881B3', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 480, + 'cur': 'USD', + 'width': 640, + 'cpm': 0.9 + } + ] +}; +const SERVER_DISPLAY_RESPONSE = { + 'source': {'aid': 12345, 'pubId': 54321}, + 'bids': [{ + 'ad': '', + 'requestId': '2e41f65424c87c', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 250, + 'cur': 'USD', + 'width': 300, + 'cpm': 0.9 + }] +}; + +const videoBidderRequest = { + bidderCode: 'bidderCode', + bids: [{mediaTypes: {video: {}}, bidId: '2e41f65424c87c'}] +}; + +const displayBidderRequest = { + bidderCode: 'bidderCode', + bids: [{bidId: '2e41f65424c87c'}] +}; + +const videoEqResponse = [{ + vastUrl: 'http://rtb.adtelligent.com/vast/?adid=44F2AEB9BFC881B3', + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'video', + netRevenue: true, + currency: 'USD', + height: 480, + width: 640, + ttl: 3600, + cpm: 0.9 +}]; + +const displayEqResponse = [{ + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'display', + netRevenue: true, + currency: 'USD', + ad: '', + height: 250, + width: 300, + ttl: 3600, + cpm: 0.9 +}]; + +describe('adtelligentBidAdapter', () => { + 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_REQUEST)).to.equal(12345); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, VIDEO_REQUEST); + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(undefined); + }); + }); + + describe('buildRequests', () => { + let videoBidRequests = [VIDEO_REQUEST]; + let displayBidRequests = [DISPLAY_REQUEST]; + let videoAndDisplayBidRequests = [DISPLAY_REQUEST, VIDEO_REQUEST]; + + const displayRequest = spec.buildRequests(displayBidRequests, {}); + const videoRequest = spec.buildRequests(videoBidRequests, {}); + const videoAndDisplayRequests = spec.buildRequests(videoAndDisplayBidRequests, {}); + + it('sends bid request to ENDPOINT via GET', () => { + expect(videoRequest.method).to.equal('GET'); + expect(displayRequest.method).to.equal('GET'); + expect(videoAndDisplayRequests.method).to.equal('GET'); + }); + + it('sends bid request to correct ENDPOINT', () => { + expect(videoRequest.url).to.equal(ENDPOINT); + expect(displayRequest.url).to.equal(ENDPOINT); + expect(videoAndDisplayRequests.url).to.equal(ENDPOINT); + }); + + it('sends correct video bid parameters', () => { + const bid = Object.assign({}, videoRequest.data); + delete bid.domain; + + const eq = { + callbackId: '84ab500420319d', + ad_type: 'video', + aid: 12345, + sizes: '480x360,640x480' + }; + + expect(bid).to.deep.equal(eq); + }); + + it('sends correct display bid parameters', () => { + const bid = Object.assign({}, displayRequest.data); + delete bid.domain; + + const eq = { + callbackId: '84ab500420319d', + ad_type: 'display', + aid: 12345, + sizes: '300x250' + }; + + expect(bid).to.deep.equal(eq); + }); + + it('sends correct video and display bid parameters', () => { + const bid = Object.assign({}, videoAndDisplayRequests.data); + delete bid.domain; + + const eq = { + callbackId: '84ab500420319d', + ad_type: 'display', + aid: 12345, + sizes: '300x250', + callbackId2: '84ab500420319d', + ad_type2: 'video', + aid2: 12345, + sizes2: '480x360,640x480' + }; + + expect(bid).to.deep.equal(eq); + }); + }); + + describe('interpretResponse', () => { + let serverResponse; + let bidderRequest; + let eqResponse; + + afterEach(() => { + serverResponse = null; + bidderRequest = null; + eqResponse = null; + }); + + it('should get correct video bid response', () => { + serverResponse = SERVER_VIDEO_RESPONSE; + bidderRequest = videoBidderRequest; + eqResponse = videoEqResponse; + + bidServerResponseCheck(); + }); + + it('should get correct display bid response', () => { + serverResponse = SERVER_DISPLAY_RESPONSE; + bidderRequest = displayBidderRequest; + eqResponse = displayEqResponse; + + bidServerResponseCheck(); + }); + + function bidServerResponseCheck() { + const result = spec.interpretResponse({body: serverResponse}, {bidderRequest}); + + expect(result).to.deep.equal(eqResponse); + } + + function nobidServerResponseCheck() { + const noBidServerResponse = {bids: []}; + const noBidResult = spec.interpretResponse({body: noBidServerResponse}, {bidderRequest}); + + expect(noBidResult.length).to.equal(0); + } + + it('handles video nobid responses', () => { + bidderRequest = videoBidderRequest; + + nobidServerResponseCheck(); + }); + + it('handles display nobid responses', () => { + bidderRequest = displayBidderRequest; + + nobidServerResponseCheck(); + }); + }); +}); diff --git a/test/spec/modules/adxcgAnalyticsAdapter_spec.js b/test/spec/modules/adxcgAnalyticsAdapter_spec.js index 790a39789b2..edd09fdf54e 100644 --- a/test/spec/modules/adxcgAnalyticsAdapter_spec.js +++ b/test/spec/modules/adxcgAnalyticsAdapter_spec.js @@ -12,41 +12,75 @@ describe('adxcg analytics adapter', () => { xhr = sinon.useFakeXMLHttpRequest(); requests = []; xhr.onCreate = request => requests.push(request); + sinon.stub(events, 'getEvents').returns([]); }); afterEach(() => { xhr.restore(); + events.getEvents.restore(); }); describe('track', () => { + let initOptions = { + publisherId: '42' + }; + + adaptermanager.registerAnalyticsAdapter({ + code: 'adxcg', + adapter: adxcgAnalyticsAdapter + }); + + beforeEach(() => { + adaptermanager.enableAnalytics({ + provider: 'adxcg', + options: initOptions + }); + }); + + afterEach(() => { + adxcgAnalyticsAdapter.disableAnalytics(); + }); + it('builds and sends auction data', () => { - let auctionTimestamp = 42; - let initOptions = { - publisherId: '42' - }; + let auctionTimestamp = 1496510254313; let bidRequest = { - requestId: 'requestIdData' + auctionId: 'requestIdData' }; let bidResponse = { adId: 'adIdData', ad: 'adContent' }; - adaptermanager.registerAnalyticsAdapter({ - code: 'adxcg', - adapter: adxcgAnalyticsAdapter - }); - - adaptermanager.enableAnalytics({ - provider: 'adxcg', - options: initOptions - }); - + let bidTimeoutArgsV1 = [ + { + 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' + } + ]; + + // Step 1: Send auction init event events.emit(constants.EVENTS.AUCTION_INIT, { timestamp: auctionTimestamp }); + + // Step 2: Send bid requested event events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + + // Step 3: Send bid response event events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + // Step 4: Send bid time out event + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); + + // Step 5: Send auction end event events.emit(constants.EVENTS.AUCTION_END, {}); expect(requests.length).to.equal(1); @@ -61,8 +95,12 @@ describe('adxcg analytics adapter', () => { expect(auctionEventData.bidResponses[0]).to.not.have.property('ad'); expect(auctionEventData.initOptions).to.deep.equal(initOptions); + expect(auctionEventData.auctionTimestamp).to.equal(auctionTimestamp); + expect(auctionEventData.bidTimeout).to.deep.equal(['bidderOne', 'bidderTwo']); + + // Step 6: Send auction bid won event events.emit(constants.EVENTS.BID_WON, { adId: 'adIdData', ad: 'adContent' diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index fa55bf92e2e..99f07aa4d53 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -1,116 +1,62 @@ -import { expect } from 'chai'; -import Adapter from 'modules/adxcgBidAdapter'; -import bidmanager from 'src/bidmanager'; +import {expect} from 'chai'; import * as url from 'src/url'; +import {spec} from 'modules/adxcgBidAdapter'; -const REQUEST = { - 'bidderCode': 'adxcg', - 'bids': [ - { +describe('AdxcgAdapter', () => { + describe('isBidRequestValid', () => { + let bid = { 'bidder': 'adxcg', 'params': { - 'adzoneid': '1', + 'adzoneid': '1' }, - 'sizes': [ - [300, 250], - [640, 360], - [1, 1] - ], + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [640, 360], [1, 1]], 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2' - } - ] -}; - -const RESPONSE = [{ - 'bidId': '84ab500420319d', - 'width': 300, - 'height': 250, - 'creativeId': '42', - 'cpm': 0.45, - 'ad': '' -}] - -const VIDEO_RESPONSE = [{ - 'bidId': '84ab500420319d', - 'width': 640, - 'height': 360, - 'creativeId': '42', - 'cpm': 0.45, - 'vastUrl': 'vastContentUrl' -}] - -const NATIVE_RESPONSE = [{ - 'bidId': '84ab500420319d', - 'width': 0, - 'height': 0, - 'creativeId': '42', - 'cpm': 0.45, - 'nativeResponse': { - 'assets': [{ - 'id': 1, - 'required': 0, - 'title': { - 'text': 'titleContent' - } - }, { - 'id': 2, - 'required': 0, - 'img': { - 'url': 'imageContent', - 'w': 600, - 'h': 600 - } - }, { - 'id': 3, - 'required': 0, - 'data': { - 'label': 'DESC', - 'value': 'descriptionContent' - } - }, { - 'id': 0, - 'required': 0, - 'data': { - 'label': 'SPONSORED', - 'value': 'sponsoredByContent' - } - }], - 'link': { - 'url': 'linkContent' - }, - 'imptrackers': ['impressionTracker1', 'impressionTracker2'] - } -}] + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '1d1a030790a475', + }; -describe('AdxcgAdapter', () => { - let adapter; - - beforeEach(() => adapter = new Adapter()); - - describe('request function', () => { - let xhr; - let requests; + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); + 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); }); + }); - afterEach(() => xhr.restore()); + describe('request function http', () => { + let bid = { + 'bidder': 'adxcg', + 'params': { + 'adzoneid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [640, 360], [1, 1]], + 'bidId': '84ab500420319d', + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '1d1a030790a475', + }; it('creates a valid adxcg request url', () => { - adapter.callBids(REQUEST); + let request = spec.buildRequests([bid]); + expect(request).to.exist; + // console.log('IS:' + JSON.stringify(request)); - let parsedRequestUrl = url.parse(requests[0].url); + expect(request.method).to.equal('GET'); + let parsedRequestUrl = url.parse(request.url); - expect(parsedRequestUrl.hostname).to.equal('ad-emea.adxcg.net'); + expect(parsedRequestUrl.hostname).to.equal('hbp.adxcg.net'); expect(parsedRequestUrl.pathname).to.equal('/get/adi'); let query = parsedRequestUrl.search; expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20141124'); + expect(query.ver).to.equal('r20171102PB10'); + expect(query.source).to.equal('pbjs10'); + 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; @@ -119,94 +65,201 @@ describe('AdxcgAdapter', () => { }); describe('response handler', () => { - let server; + let BIDDER_REQUEST = { + 'bidder': 'adxcg', + 'params': { + 'adzoneid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [640, 360], [1, 1]], + 'bidId': '84ab500420319d', + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '1d1a030790a475', + }; - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); - }); + let BANNER_RESPONSE = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 300, + 'height': 250, + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'ad': '' + }], + header: {'someheader': 'fakedata'} + } - afterEach(() => { - server.restore() - bidmanager.addBidResponse.restore(); - }); + let BANNER_RESPONSE_WITHDEALID = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 300, + 'height': 250, + 'deal_id': '7722', + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'ad': '' + }], + header: {'someheader': 'fakedata'} + } + + let VIDEO_RESPONSE = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 640, + 'height': 360, + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'vastUrl': 'vastContentUrl' + }], + header: {'someheader': 'fakedata'} + } + + let NATIVE_RESPONSE = + { + body: [{ + 'bidId': '84ab500420319d', + 'bidderCode': 'adxcg', + 'width': 0, + 'height': 0, + 'creativeId': '42', + 'cpm': 0.45, + 'currency': 'USD', + 'netRevenue': true, + 'nativeResponse': { + 'assets': [{ + 'id': 1, + 'required': 0, + 'title': { + 'text': 'titleContent' + } + }, { + 'id': 2, + 'required': 0, + 'img': { + 'url': 'imageContent', + 'w': 600, + 'h': 600 + } + }, { + 'id': 3, + 'required': 0, + 'data': { + 'label': 'DESC', + 'value': 'descriptionContent' + } + }, { + 'id': 0, + 'required': 0, + 'data': { + 'label': 'SPONSORED', + 'value': 'sponsoredByContent' + } + }], + 'link': { + 'url': 'linkContent' + }, + 'imptrackers': ['impressionTracker1', 'impressionTracker2'] + } + }], + header: {'someheader': 'fakedata'} + } it('handles regular responses', () => { - server.respondWith(JSON.stringify(RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.bidderCode).to.equal('adxcg'); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); - expect(bidResponse.statusMessage).to.equal('Bid available'); - expect(bidResponse.adId).to.equal('84ab500420319d'); - expect(bidResponse.mediaType).to.equal('banner'); - expect(bidResponse.creative_id).to.equal('42'); - expect(bidResponse.code).to.equal('adxcg'); - expect(bidResponse.cpm).to.equal(0.45); - expect(bidResponse.ad).to.equal(''); + let result = spec.interpretResponse(BANNER_RESPONSE, BIDDER_REQUEST); + + expect(result).to.have.lengthOf(1); + + expect(result[0]).to.exist; + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].ad).to.equal(''); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + expect(result[0].dealId).to.not.exist; + }); + + it('handles regular responses with dealid', () => { + let result = spec.interpretResponse(BANNER_RESPONSE_WITHDEALID, BIDDER_REQUEST); + + expect(result).to.have.lengthOf(1); + + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].ad).to.equal(''); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); }); it('handles video responses', () => { - server.respondWith(JSON.stringify(VIDEO_RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.bidderCode).to.equal('adxcg'); - expect(bidResponse.width).to.equal(640); - expect(bidResponse.height).to.equal(360); - expect(bidResponse.statusMessage).to.equal('Bid available'); - expect(bidResponse.adId).to.equal('84ab500420319d'); - expect(bidResponse.mediaType).to.equal('video'); - expect(bidResponse.creative_id).to.equal('42'); - expect(bidResponse.code).to.equal('adxcg'); - expect(bidResponse.cpm).to.equal(0.45); - expect(bidResponse.vastUrl).to.equal('vastContentUrl'); - expect(bidResponse.descriptionUrl).to.equal('vastContentUrl'); + let result = spec.interpretResponse(VIDEO_RESPONSE, BIDDER_REQUEST); + expect(result).to.have.lengthOf(1); + + expect(result[0].width).to.equal(640); + expect(result[0].height).to.equal(360); + expect(result[0].mediaType).to.equal('video'); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].vastUrl).to.equal('vastContentUrl'); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); }); it('handles native responses', () => { - server.respondWith(JSON.stringify(NATIVE_RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.bidderCode).to.equal('adxcg'); - expect(bidResponse.width).to.equal(0); - expect(bidResponse.height).to.equal(0); - expect(bidResponse.statusMessage).to.equal('Bid available'); - expect(bidResponse.adId).to.equal('84ab500420319d'); - expect(bidResponse.mediaType).to.equal('native'); - expect(bidResponse.creative_id).to.equal('42'); - expect(bidResponse.code).to.equal('adxcg'); - expect(bidResponse.cpm).to.equal(0.45); - - expect(bidResponse.native.clickUrl).to.equal('linkContent'); - expect(bidResponse.native.impressionTrackers).to.deep.equal(['impressionTracker1', 'impressionTracker2']); - expect(bidResponse.native.title).to.equal('titleContent'); - expect(bidResponse.native.image).to.equal('imageContent'); - expect(bidResponse.native.body).to.equal('descriptionContent'); - expect(bidResponse.native.sponsoredBy).to.equal('sponsoredByContent'); + let result = spec.interpretResponse(NATIVE_RESPONSE, BIDDER_REQUEST); + + expect(result[0].width).to.equal(0); + expect(result[0].height).to.equal(0); + expect(result[0].mediaType).to.equal('native'); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + + 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.body).to.equal('descriptionContent'); + expect(result[0].native.sponsoredBy).to.equal('sponsoredByContent'); }); it('handles nobid responses', () => { - server.respondWith('[]'); + let response = []; + let bidderRequest = BIDDER_REQUEST; + + let result = spec.interpretResponse(response, bidderRequest); + expect(result.length).to.equal(0); + }); + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + describe('getUserSyncs', () => { + let syncoptionsIframe = { + 'iframeEnabled': 'true' + }; - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.statusMessage).to.equal('Bid returned empty or error response'); + it('should return iframe sync option', () => { + expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.equal('iframe'); + expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal('//cdn.adxcg.net/pb-sync.html'); }); }); }); diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index 05c112c2a99..cb9126619f2 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -1,25 +1,22 @@ import { expect } from 'chai'; import { parse } from '../../../src/url'; -import AdyoulikAdapter from '../../../modules/adyoulikeBidAdapter'; -import bidmanager from 'src/bidmanager'; -import { STATUS } from 'src/constants'; + +import { spec } from 'modules/adyoulikeBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; describe('Adyoulike Adapter', () => { - const endpoint = 'http://hb-api.omnitagjs.com/hb-api/prebid'; const canonicalUrl = 'http://canonical.url/?t=%26'; + const defaultDC = 'hb-api'; const bidderCode = 'adyoulike'; - const bidRequestWithEmptyPlacement = { - 'bidderCode': 'adyoulike', - 'bids': [ - { - 'bidId': 'bid_id_0', - 'bidder': 'adyoulike', - 'placementCode': 'adunit/hb-0', - 'params': {}, - 'sizes': '300x250' - } - ], - }; + const bidRequestWithEmptyPlacement = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': {}, + 'sizes': '300x250' + } + ]; const bidRequestWithEmptySizes = { 'bidderCode': 'adyoulike', 'bids': [ @@ -34,63 +31,73 @@ describe('Adyoulike Adapter', () => { } ], }; - const bidRequestWithSinglePlacement = { - 'bidderCode': 'adyoulike', - 'bids': [ - { - 'bidId': 'bid_id_0', - 'bidder': 'adyoulike', - 'placementCode': 'adunit/hb-0', - 'params': { - 'placement': 'placement_0' - }, - 'sizes': '300x250', - 'transactionId': 'bid_id_0_transaction_id' - } - ], - }; - const bidRequestMultiPlacements = { - 'bidderCode': 'adyoulike', - 'bids': [ - { - 'bidId': 'bid_id_0', - 'bidder': 'adyoulike', - 'placementCode': 'adunit/hb-0', - 'params': { - 'placement': 'placement_0' - }, - 'sizes': '300x250', - 'transactionId': 'bid_id_0_transaction_id' + + const bidRequestWithSinglePlacement = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' }, - { - 'bidId': 'bid_id_1', - 'bidder': 'adyoulike', - 'placementCode': 'adunit/hb-1', - 'params': { - 'placement': 'placement_1' - }, - 'sizes': [[300, 600]], - 'transactionId': 'bid_id_1_transaction_id' + 'sizes': '300x250', + 'transactionId': 'bid_id_0_transaction_id' + } + ]; + + const bidRequestWithDCPlacement = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0', + 'DC': 'fra01' }, - { - 'bidId': 'bid_id_2', - 'bidder': 'adyoulike', - 'placementCode': 'adunit/hb-2', - 'params': {}, - 'sizes': '300x400', - 'transactionId': 'bid_id_2_transaction_id' + 'sizes': '300x250', + 'transactionId': 'bid_id_0_transaction_id' + } + ]; + + const bidRequestMultiPlacements = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' }, - { - 'bidId': 'bid_id_3', - 'bidder': 'adyoulike', - 'placementCode': 'adunit/hb-3', - 'params': { - 'placement': 'placement_3' - }, - 'transactionId': 'bid_id_3_transaction_id' - } - ], - }; + 'sizes': '300x250', + 'transactionId': 'bid_id_0_transaction_id' + }, + { + 'bidId': 'bid_id_1', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-1', + 'params': { + 'placement': 'placement_1' + }, + 'sizes': [[300, 600]], + 'transactionId': 'bid_id_1_transaction_id' + }, + { + 'bidId': 'bid_id_2', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-2', + 'params': {}, + 'sizes': '300x400', + 'transactionId': 'bid_id_2_transaction_id' + }, + { + 'bidId': 'bid_id_3', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-3', + 'params': { + 'placement': 'placement_3' + }, + 'transactionId': 'bid_id_3_transaction_id' + } + ]; const responseWithEmptyPlacement = [ { @@ -99,55 +106,79 @@ describe('Adyoulike Adapter', () => { ]; const responseWithSinglePlacement = [ { + 'BidID': 'bid_id_0', 'Placement': 'placement_0', - 'Banner': 'placement_0', - 'Price': 0.5 + 'Ad': 'placement_0', + 'Price': 0.5, + 'Height': 300, + 'Width': 300, } ]; const responseWithMultiplePlacements = [ { + 'BidID': 'bid_id_0', 'Placement': 'placement_0', - 'Banner': 'placement_0', - 'Price': 0.5 + 'Ad': 'placement_0', + 'Price': 0.5, + 'Height': 300, + 'Width': 300, }, { + 'BidID': 'bid_id_1', 'Placement': 'placement_1', - 'Banner': 'placement_1', - 'Price': 0.6 + 'Ad': 'placement_1', + 'Price': 0.6, + 'Height': 300, + 'Width': 300, } ]; + const adapter = newBidder(spec); - let adapter; + let getEndpoint = (dc = defaultDC) => `http://${dc}.omnitagjs.com/hb-api/prebid`; - beforeEach(() => { - adapter = new AdyoulikAdapter(); + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); }); - describe('adapter public API', () => { - const adapter = new AdyoulikAdapter(); + describe('isBidRequestValid', () => { + let bid = { + 'bidId': 'bid_id_1', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-1', + 'params': { + 'placement': 'placement_1' + }, + 'sizes': [[300, 600]], + 'transactionId': 'bid_id_1_transaction_id' + }; - it('setBidderCode', () => { - expect(adapter.setBidderCode).to.be.a('function'); + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('callBids', () => { - expect(adapter.setBidderCode).to.be.a('function'); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.size; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placement': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); - describe('request function', () => { - let requests; - let xhr; - let addBidResponse; + describe('buildRequests', () => { let canonicalQuery; beforeEach(() => { - requests = []; - - xhr = sinon.useFakeXMLHttpRequest(); - xhr.onCreate = request => requests.push(request); - - addBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - let canonical = document.createElement('link'); canonical.rel = 'canonical'; canonical.href = canonicalUrl; @@ -156,159 +187,114 @@ describe('Adyoulike Adapter', () => { }); afterEach(() => { - xhr.restore(); - bidmanager.addBidResponse.restore(); canonicalQuery.restore(); }); - it('requires placement request', () => { - adapter.callBids(bidRequestWithEmptyPlacement); - expect(requests).to.be.empty; - expect(addBidResponse.calledOnce).to.equal(false); - }); - - it('requires sizes in request', () => { - adapter.callBids(bidRequestWithEmptySizes); - expect(requests).to.be.empty; - expect(addBidResponse.calledOnce).to.equal(false); - }); - it('sends bid request to endpoint with single placement', () => { - adapter.callBids(bidRequestWithSinglePlacement); - expect(requests[0].url).to.contain(endpoint); - expect(requests[0].method).to.equal('POST'); + const request = spec.buildRequests(bidRequestWithSinglePlacement); + const payload = JSON.parse(request.data); - expect(requests[0].url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + expect(request.url).to.contain(getEndpoint()); + expect(request.method).to.equal('POST'); + expect(request.url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); - let body = JSON.parse(requests[0].requestBody); - expect(body.Version).to.equal('0.2'); - expect(body.Placements).deep.equal(['placement_0']); - expect(body.PageRefreshed).to.equal(false); - expect(body.TransactionIds).deep.equal({'placement_0': 'bid_id_0_transaction_id'}); + expect(payload.Version).to.equal('1.0'); + expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); + expect(payload.PageRefreshed).to.equal(false); + expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); it('sends bid request to endpoint with single placement without canonical', () => { canonicalQuery.restore(); + const request = spec.buildRequests(bidRequestWithSinglePlacement); + const payload = JSON.parse(request.data); - adapter.callBids(bidRequestWithSinglePlacement); - expect(requests[0].url).to.contain(endpoint); - expect(requests[0].method).to.equal('POST'); - - expect(requests[0].url).to.not.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + expect(request.url).to.contain(getEndpoint()); + expect(request.method).to.equal('POST'); - let body = JSON.parse(requests[0].requestBody); - expect(body.Version).to.equal('0.2'); - expect(body.Placements).deep.equal(['placement_0']); - expect(body.PageRefreshed).to.equal(false); - expect(body.TransactionIds).deep.equal({'placement_0': 'bid_id_0_transaction_id'}); + expect(request.url).to.not.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + expect(payload.Version).to.equal('1.0'); + expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); + expect(payload.PageRefreshed).to.equal(false); + expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); it('sends bid request to endpoint with multiple placements', () => { - adapter.callBids(bidRequestMultiPlacements); - expect(requests[0].url).to.contain(endpoint); - expect(requests[0].method).to.equal('POST'); + const request = spec.buildRequests(bidRequestMultiPlacements); + const payload = JSON.parse(request.data); + expect(request.url).to.contain(getEndpoint()); + expect(request.method).to.equal('POST'); - expect(requests[0].url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); - - let body = JSON.parse(requests[0].requestBody); - expect(body.Version).to.equal('0.2'); - expect(body.Placements).deep.equal(['placement_0', 'placement_1']); - expect(body.PageRefreshed).to.equal(false); - expect(body.TransactionIds).deep.equal({'placement_0': 'bid_id_0_transaction_id', 'placement_1': 'bid_id_1_transaction_id'}); - }); - }); + expect(request.url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); - describe('response function', () => { - let server; - let addBidResponse; + expect(payload.Version).to.equal('1.0'); - beforeEach(() => { - server = sinon.fakeServer.create(); - addBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - }); + expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); + expect(payload.Bids['bid_id_1'].PlacementID).to.be.equal('placement_1'); + expect(payload.Bids['bid_id_3'].PlacementID).to.be.equal('placement_3'); - afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); + expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); + expect(payload.Bids['bid_id_1'].TransactionID).to.be.equal('bid_id_1_transaction_id'); + expect(payload.Bids['bid_id_3'].TransactionID).to.be.equal('bid_id_3_transaction_id'); + expect(payload.PageRefreshed).to.equal(false); }); - it('invalid json', () => { - server.respondWith('{'); - adapter.callBids(bidRequestWithSinglePlacement); - server.respond(); + it('sends bid request to endpoint setted by parameters', () => { + const request = spec.buildRequests(bidRequestWithDCPlacement); + const payload = JSON.parse(request.data); - expect(addBidResponse.calledOnce).to.equal(true); - expect(addBidResponse.args[0]).to.have.lengthOf(2); - expect(addBidResponse.args[0][1].getStatusCode()).to.equal(STATUS.NO_BID); - expect(addBidResponse.args[0][1].bidderCode).to.equal(bidderCode); + expect(request.url).to.contain(getEndpoint(`${defaultDC}-fra01`)); }); + }); + // + describe('interpretResponse', () => { + let serverResponse; - it('receive reponse with empty placement', () => { - server.respondWith(JSON.stringify(responseWithEmptyPlacement)); - adapter.callBids(bidRequestWithSinglePlacement); - server.respond(); + beforeEach(() => { + serverResponse = { + body: {} + } + }); - expect(addBidResponse.calledOnce).to.equal(true); - expect(addBidResponse.args[0]).to.have.lengthOf(2); - expect(addBidResponse.args[0][1].getStatusCode()).to.equal(STATUS.NO_BID); - expect(addBidResponse.args[0][1].bidderCode).to.equal(bidderCode); + it('handles nobid responses', () => { + let response = [{ + BidID: '123dfsdf', + Attempt: '32344fdse1', + Placement: '12df1' + }]; + serverResponse.body = response; + let result = spec.interpretResponse(serverResponse, []); + expect(result).deep.equal([]); }); it('receive reponse with single placement', () => { - server.respondWith(JSON.stringify(responseWithSinglePlacement)); - adapter.callBids(bidRequestWithSinglePlacement); - server.respond(); - - expect(addBidResponse.calledOnce).to.equal(true); - expect(addBidResponse.args[0]).to.have.lengthOf(2); - expect(addBidResponse.args[0][1].getStatusCode()).to.equal(STATUS.GOOD); - expect(addBidResponse.args[0][1].cpm).to.equal(0.5); - expect(addBidResponse.args[0][1].ad).to.equal('placement_0'); - expect(addBidResponse.args[0][1].width).to.equal(300); - expect(addBidResponse.args[0][1].height).to.equal(250); + serverResponse.body = responseWithSinglePlacement; + let result = spec.interpretResponse(serverResponse, bidRequestWithSinglePlacement); + + expect(result.length).to.equal(1); + expect(result[0].cpm).to.equal(0.5); + expect(result[0].ad).to.equal('placement_0'); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(300); }); it('receive reponse with multiple placement', () => { - server.respondWith(JSON.stringify(responseWithMultiplePlacements)); - adapter.callBids(bidRequestMultiPlacements); - server.respond(); - - expect(addBidResponse.calledTwice).to.equal(true); - - expect(addBidResponse.args[0]).to.have.lengthOf(2); - expect(addBidResponse.args[0][1].getStatusCode()).to.equal(STATUS.GOOD); - expect(addBidResponse.args[0][1].bidderCode).to.equal(bidderCode); - expect(addBidResponse.args[0][1].cpm).to.equal(0.5); - expect(addBidResponse.args[0][1].ad).to.equal('placement_0'); - expect(addBidResponse.args[0][1].width).to.equal(300); - expect(addBidResponse.args[0][1].height).to.equal(250); - - expect(addBidResponse.args[1]).to.have.lengthOf(2); - expect(addBidResponse.args[1][1].getStatusCode()).to.equal(STATUS.GOOD); - expect(addBidResponse.args[1][1].bidderCode).to.equal(bidderCode); - expect(addBidResponse.args[1][1].cpm).to.equal(0.6); - expect(addBidResponse.args[1][1].ad).to.equal('placement_1'); - expect(addBidResponse.args[1][1].width).to.equal(300); - expect(addBidResponse.args[1][1].height).to.equal(600); - }); - - it('receive reponse with invalid placement number', () => { - server.respondWith(JSON.stringify(responseWithSinglePlacement)); - adapter.callBids(bidRequestMultiPlacements); - server.respond(); - - expect(addBidResponse.calledTwice).to.equal(true); - - expect(addBidResponse.args[0]).to.have.lengthOf(2); - expect(addBidResponse.args[0][1].getStatusCode()).to.equal(STATUS.GOOD); - expect(addBidResponse.args[0][1].bidderCode).to.equal(bidderCode); - expect(addBidResponse.args[0][1].cpm).to.equal(0.5); - expect(addBidResponse.args[0][1].ad).to.equal('placement_0'); - expect(addBidResponse.args[0][1].width).to.equal(300); - expect(addBidResponse.args[0][1].height).to.equal(250); - - expect(addBidResponse.args[1]).to.have.lengthOf(2); - expect(addBidResponse.args[1][1].getStatusCode()).to.equal(STATUS.NO_BID); + serverResponse.body = responseWithMultiplePlacements; + let result = spec.interpretResponse(serverResponse, bidRequestMultiPlacements); + + 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); + expect(result[1].height).to.equal(300); }); }); }); diff --git a/test/spec/modules/aerservBidAdapter_spec.js b/test/spec/modules/aerservBidAdapter_spec.js deleted file mode 100644 index be0f6393063..00000000000 --- a/test/spec/modules/aerservBidAdapter_spec.js +++ /dev/null @@ -1,213 +0,0 @@ -import {expect} from 'chai'; -import AerServAdapter from 'modules/aerservBidAdapter'; -import bidmanager from 'src/bidmanager'; - -const BASE_REQUEST = JSON.stringify({ - bidderCode: 'aerserv', - requestId: 'a595eff7-d5a3-40f8-971c-5b4ef244ec53', - bidderRequestId: '1f8c8c03de01f9', - bids: [ - { - bidder: 'aerserv', - params: { - plc: '480', - }, - placementCode: 'adunit-1', - transactionId: 'a0e033af-f50c-4a7e-aeed-c01c5f709848', - sizes: [[300, 250], [300, 600]], - bidId: '2f4a69463b3bc9', - bidderRequestId: '1f8c8c03de01f9', - requestId: 'a595eff7-d5a3-40f8-971c-5b4ef244ec53' - } - ] -}); - -describe('AerServ Adapter', () => { - let adapter; - let bidmanagerStub; - - beforeEach(() => { - adapter = new AerServAdapter(); - bidmanagerStub = sinon.stub(bidmanager, 'addBidResponse'); - }); - - afterEach(() => { - bidmanager.addBidResponse.restore(); - }); - - describe('callBids()', () => { - let xhr; - let requests; - - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - }); - - afterEach(() => { - xhr.restore(); - }); - - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - - it('should not add bid responses with no bids to call', () => { - adapter.callBids({}); - - sinon.assert.notCalled(bidmanager.addBidResponse); - }); - - it('requires plc parameter to make request', () => { - let bidRequest = JSON.parse(BASE_REQUEST); - bidRequest.bids[0].params = {}; - adapter.callBids(bidRequest); - expect(requests).to.be.empty; - }); - - it('sends requests to normal endpoint for non-video requests', () => { - adapter.callBids(JSON.parse(BASE_REQUEST)); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.include('/as/json/pbjs/v1'); - }); - - it('sends requests to video endpoint for video requests', () => { - let bidRequest = JSON.parse(BASE_REQUEST); - bidRequest.bids[0]['mediaType'] = 'video'; - bidRequest.bids[0]['video'] = {}; - adapter.callBids(bidRequest); - expect(requests[0].url).to.include('/as/json/pbjsvast/v1'); - }); - - it('properly adds video parameters to the request', () => { - let bidRequest = JSON.parse(BASE_REQUEST); - bidRequest.bids[0]['mediaType'] = 'video'; - bidRequest.bids[0].params['video'] = { videoParam: 'videoValue' }; - adapter.callBids(bidRequest); - expect(requests[0].url).to.include('videoParam=videoValue'); - }); - - it('parses the first size for video requests', () => { - let bidRequest = JSON.parse(BASE_REQUEST); - bidRequest.bids[0]['mediaType'] = 'video'; - adapter.callBids(bidRequest); - expect(requests[0].url).to.include('vpw=300'); - expect(requests[0].url).to.include('vph=250'); - }); - - it('sends requests to production by default', () => { - adapter.callBids(JSON.parse(BASE_REQUEST)); - expect(requests[0].url).to.include('//ads.aerserv.com'); - }); - - it('sends requests to the specified endpoint when \'env\' is provided', () => { - let bidRequest = JSON.parse(BASE_REQUEST); - bidRequest.bids[0].params['env'] = 'dev'; - adapter.callBids(bidRequest); - expect(requests[0].url).to.include('//dev-ads.aerserv.com'); - }); - }); - - describe('response handling', () => { - let server; - - beforeEach(() => { - server = sinon.fakeServer.create(); - }); - - afterEach(() => { - server.restore(); - }); - - it('responds with an empty bid without required parameters', () => { - let bidRequest = JSON.parse(BASE_REQUEST); - bidRequest.bids[0].params = {}; - adapter.callBids(bidRequest); - let bid = bidmanagerStub.getCall(0).args[1]; - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bid.getStatusCode()).to.equal(2); - }); - - it('responds with an empty bid on empty response', () => { - server.respondWith(''); - - adapter.callBids(JSON.parse(BASE_REQUEST)); - server.respond(); - let bid = bidmanagerStub.getCall(0).args[1]; - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bid.getStatusCode()).to.equal(2); - }); - - it('responds with an empty bid on un-parseable JSON response', () => { - server.respondWith('{\"bad\":\"json}'); - - adapter.callBids(JSON.parse(BASE_REQUEST)); - server.respond(); - let bid = bidmanagerStub.getCall(0).args[1]; - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bid.getStatusCode()).to.equal(2); - }); - - it('responds with a valid bid returned ad', () => { - server.respondWith(JSON.stringify({cpm: 5, w: 320, h: 50, adm: 'sweet ad markup'})); - adapter.callBids(JSON.parse(BASE_REQUEST)); - server.respond(); - let bid = bidmanagerStub.getCall(0).args[1]; - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bid.getStatusCode()).to.equal(1); - }); - - it('responds with a valid bid from returned ad', () => { - server.respondWith(JSON.stringify({cpm: 5, w: 320, h: 50, vastUrl: 'sweet URL where VAST is at'})); - let bidRequest = JSON.parse(BASE_REQUEST); - bidRequest.bids[0]['mediaType'] = 'video'; - bidRequest.bids[0]['video'] = {}; - adapter.callBids(bidRequest); - server.respond(); - let bid = bidmanagerStub.getCall(0).args[1]; - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bid.getStatusCode()).to.equal(1); - }); - - it('responds with empty bid if response has no ad', () => { - server.respondWith(JSON.stringify({error: 'no ads'})); - adapter.callBids(JSON.parse(BASE_REQUEST)); - server.respond(); - let bid = bidmanagerStub.getCall(0).args[1]; - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bid.getStatusCode()).to.equal(2); - }); - - // things that should never occur - it('responds with empty bid if response has 0 or below cpm', () => { - server.respondWith(JSON.stringify({cpm: 0, w: 320, h: 50, adm: 'sweet ad markup'})); - adapter.callBids(JSON.parse(BASE_REQUEST)); - server.respond(); - let bid = bidmanagerStub.getCall(0).args[1]; - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bid.getStatusCode()).to.equal(2); - }); - - it('responds with empty bid if response has no markup', () => { - server.respondWith(JSON.stringify({cpm: 5.0, w: 320, h: 50})); - adapter.callBids(JSON.parse(BASE_REQUEST)); - server.respond(); - let bid = bidmanagerStub.getCall(0).args[1]; - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bid.getStatusCode()).to.equal(2); - }); - - it('responds with an empty bid if response has no video markup', () => { - server.respondWith(JSON.stringify({cpm: 5, w: 320, h: 50})); - let bidRequest = JSON.parse(BASE_REQUEST); - bidRequest.bids[0]['mediaType'] = 'video'; - bidRequest.bids[0]['video'] = {}; - adapter.callBids(bidRequest); - server.respond(); - let bid = bidmanagerStub.getCall(0).args[1]; - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bid.getStatusCode()).to.equal(2); - }); - }); -}); diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index 188c5375b89..d69b9e6e3d8 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -1,7 +1,9 @@ import {expect} from 'chai'; import * as utils from 'src/utils'; -import AolAdapter from 'modules/aolBidAdapter'; -import bidmanager from 'src/bidmanager'; +import {spec} from 'modules/aolBidAdapter'; +import {config} from 'src/config'; + +const DEFAULT_AD_CONTENT = ''; let getDefaultBidResponse = () => { return { @@ -12,10 +14,11 @@ let getDefaultBidResponse = () => { id: 1, impid: '245730051428950632', price: 0.09, - adm: '', - crid: '0', + adm: DEFAULT_AD_CONTENT, + crid: 'creative-id', h: 90, w: 728, + dealid: 'deal-id', ext: {sizeid: 225} }] }] @@ -53,29 +56,32 @@ let getNexagePostBidParams = () => { let getDefaultBidRequest = () => { return { bidderCode: 'aol', - requestId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', + auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', bidderRequestId: '7101db09af0db2', start: new Date().getTime(), bids: [{ bidder: 'aol', bidId: '84ab500420319d', bidderRequestId: '7101db09af0db2', - requestId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', + auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', placementCode: 'foo', params: getMarketplaceBidParams() }] }; }; -describe('AolAdapter', () => { - const MARKETPLACE_URL = 'adserver-us.adtech.advertising.com/pubapi/3.0/'; - const NEXAGE_URL = 'hb.nexage.com/bidRequest?'; - - let adapter; +let getPixels = () => { + return ''; +}; - beforeEach(() => adapter = new AolAdapter()); +describe('AolAdapter', () => { + const MARKETPLACE_URL = '//adserver-us.adtech.advertising.com/pubapi/3.0/'; + const NEXAGE_URL = '//hb.nexage.com/bidRequest?'; + const ONE_DISPLAY_TTL = 60; + const ONE_MOBILE_TTL = 3600; - function createBidderRequest({bids, params} = {}) { + function createCustomBidRequest({bids, params} = {}) { var bidderRequest = getDefaultBidRequest(); if (bids && Array.isArray(bids)) { bidderRequest.bids = bids; @@ -86,620 +92,581 @@ describe('AolAdapter', () => { return bidderRequest; } - describe('callBids()', () => { - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); + describe('interpretResponse()', () => { + let bidderSettingsBackup; + let bidResponse; + let bidRequest; + let logWarnSpy; + let formatPixelsStub; + let isOneMobileBidderStub; + + beforeEach(() => { + bidderSettingsBackup = $$PREBID_GLOBAL$$.bidderSettings; + bidRequest = { + bidderCode: 'test-bidder-code', + bidId: 'bid-id', + ttl: 1234 + }; + bidResponse = { + body: getDefaultBidResponse() + }; + logWarnSpy = sinon.spy(utils, 'logWarn'); + formatPixelsStub = sinon.stub(spec, 'formatPixels'); + isOneMobileBidderStub = sinon.stub(spec, 'isOneMobileBidder'); }); - describe('bid request', () => { - describe('Marketplace api', () => { - let xhr; - let requests; + afterEach(() => { + $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsBackup; + logWarnSpy.restore(); + formatPixelsStub.restore(); + isOneMobileBidderStub.restore(); + }); - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - }); + it('should return formatted bid response with required properties', () => { + let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest); + expect(formattedBidResponse).to.deep.equal({ + bidderCode: bidRequest.bidderCode, + requestId: 'bid-id', + ad: DEFAULT_AD_CONTENT, + cpm: 0.09, + width: 728, + height: 90, + creativeId: 'creative-id', + pubapiId: '245730051428950632', + currency: 'USD', + dealId: 'deal-id', + netRevenue: true, + ttl: bidRequest.ttl + }); + }); - afterEach(() => xhr.restore()); + it('should add pixels to ad content when pixels are present in the response', () => { + bidResponse.body.ext = { + pixels: 'pixels-content' + }; - it('requires parameters to be made', () => { - adapter.callBids({}); - expect(requests).to.be.empty; - }); + formatPixelsStub.returns('pixels-content'); + let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest); - it('should hit the Marketplace api endpoint with the Marketplace config', () => { - adapter.callBids(getDefaultBidRequest()); - expect(requests[0].url).to.contain(MARKETPLACE_URL); - }); + 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; + }); + }); - it('should hit the Marketplace via onedisplay bidder code', () => { - let bidRequest = createBidderRequest({ - bids: [{ - bidder: 'onedisplay' - }], - params: getMarketplaceBidParams() - }); + describe('buildRequests()', () => { + it('method exists and is a function', () => { + expect(spec.buildRequests).to.exist.and.to.be.a('function'); + }); - adapter.callBids(bidRequest); - expect(requests[0].url).to.contain(MARKETPLACE_URL); - }); + describe('Marketplace', () => { + it('should not return request when no bids are present', () => { + let [request] = spec.buildRequests([]); + expect(request).to.be.empty; + }); - it('should hit the Marketplace via onedisplay bidder code when Marketplace and Nexage params are present', () => { - let bidParams = Object.assign(getMarketplaceBidParams(), getNexageGetBidParams()); - let bidRequest = createBidderRequest({ - bids: [{ - bidder: 'onedisplay' - }], - params: bidParams - }); - - adapter.callBids(bidRequest); - expect(requests[0].url).to.contain(MARKETPLACE_URL); - }); + it('should return request for Marketplace endpoint', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(MARKETPLACE_URL); + }); - it('should hit the Marketplace via onedisplay bidder code when Nexage params are present', () => { - let bidParams = Object.assign(getMarketplaceBidParams(), getNexageGetBidParams(), getNexagePostBidParams()); - let bidRequest = createBidderRequest({ - bids: [{ - bidder: 'onedisplay' - }], - params: bidParams - }); - - adapter.callBids(bidRequest); - expect(requests[0].url).to.contain(MARKETPLACE_URL); + it('should return request for Marketplace via onedisplay bidder code', () => { + let bidRequest = createCustomBidRequest({ + bids: [{ + bidder: 'onedisplay' + }], + params: getMarketplaceBidParams() }); - it('should not resolve endpoint for onedisplay bidder code when only Nexage params are present', () => { - let bidParams = Object.assign(getNexageGetBidParams(), getNexagePostBidParams()); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(MARKETPLACE_URL); + }); - adapter.callBids(createBidderRequest({ - bids: [{ - bidder: 'onedisplay' - }], - params: bidParams - })); - expect(requests.length).to.equal(0); + it('should return Marketplace request via onedisplay bidder code when' + + 'Marketplace and One Mobile GET params are present', () => { + let bidParams = Object.assign(getMarketplaceBidParams(), getNexageGetBidParams()); + let bidRequest = createCustomBidRequest({ + bids: [{ + bidder: 'onedisplay' + }], + params: bidParams }); - it('should hit endpoint based on the region config option', () => { - adapter.callBids(createBidderRequest({ - params: { - placement: 1234567, - network: '9599.1', - region: 'eu' - } - })); - expect(requests[0].url).to.contain('adserver-eu.adtech.advertising.com/pubapi/3.0/'); - }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(MARKETPLACE_URL); + }); - it('should hit the default endpoint in case of unknown region config option', () => { - adapter.callBids(createBidderRequest({ - params: { - placement: 1234567, - network: '9599.1', - region: 'an' - } - })); - expect(requests[0].url).to.contain(MARKETPLACE_URL); + it('should return Marketplace request via onedisplay bidder code when' + + 'Marketplace and One Mobile GET + POST params are present', () => { + let bidParams = Object.assign(getMarketplaceBidParams(), getNexageGetBidParams(), getNexagePostBidParams()); + let bidRequest = createCustomBidRequest({ + bids: [{ + bidder: 'onedisplay' + }], + params: bidParams }); - it('should hit endpoint based on the server config option', () => { - adapter.callBids(createBidderRequest({ - params: { - placement: 1234567, - network: '9599.1', - server: 'adserver-eu.adtech.advertising.com' - } - })); - expect(requests[0].url).to.contain('adserver-eu.adtech.advertising.com/pubapi/3.0/'); - }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(MARKETPLACE_URL); + }); - it('should be the pubapi bid request', () => { - adapter.callBids(getDefaultBidRequest()); - expect(requests[0].url).to.contain('cmd=bid;'); + it('should not resolve endpoint for onedisplay bidder code ' + + 'when only One Mobile params are present', () => { + let bidParams = Object.assign(getNexageGetBidParams(), getNexagePostBidParams()); + let bidRequest = createCustomBidRequest({ + bids: [{ + bidder: 'onedisplay' + }], + params: bidParams }); - it('should be the version 2 of pubapi', () => { - adapter.callBids(getDefaultBidRequest()); - expect(requests[0].url).to.contain('v=2;'); - }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request).to.be.empty; + }); - it('should contain cache busting', () => { - adapter.callBids(getDefaultBidRequest()); - expect(requests[0].url).to.match(/misc=\d+/); + it('should return Marketplace URL for eu region', () => { + let bidRequest = createCustomBidRequest({ + params: { + placement: 1234567, + network: '9599.1', + region: 'eu' + } }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('adserver-eu.adtech.advertising.com/pubapi/3.0/'); + }); - it('should contain required params - placement & network', () => { - adapter.callBids(createBidderRequest({ - params: { - placement: 1234567, - network: '9599.1' - } - })); - expect(requests[0].url).to.contain('/pubapi/3.0/9599.1/1234567/'); + it('should return Marketplace URL for eu region when server option is present', () => { + let bidRequest = createCustomBidRequest({ + params: { + placement: 1234567, + network: '9599.1', + server: 'adserver-eu.adtech.advertising.com' + } }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('adserver-eu.adtech.advertising.com/pubapi/3.0/'); + }); - it('should contain pageId and sizeId of 0 if params are missing', () => { - adapter.callBids(createBidderRequest({ - params: { - placement: 1234567, - network: '9599.1' - } - })); - expect(requests[0].url).to.contain('/pubapi/3.0/9599.1/1234567/0/0/ADTECH;'); + it('should return default Marketplace URL in case of unknown region config option', () => { + let bidRequest = createCustomBidRequest({ + params: { + placement: 1234567, + network: '9599.1', + region: 'an' + } }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(MARKETPLACE_URL); + }); - it('should contain pageId optional param', () => { - adapter.callBids(createBidderRequest({ - params: { - placement: 1234567, - network: '9599.1', - pageId: 12345 - } - })); - expect(requests[0].url).to.contain('/pubapi/3.0/9599.1/1234567/12345/'); - }); + it('should return url with pubapi bid option', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('cmd=bid;'); + }); - it('should contain sizeId optional param', () => { - adapter.callBids(createBidderRequest({ - params: { - placement: 1234567, - network: '9599.1', - sizeId: 12345 - } - })); - expect(requests[0].url).to.contain('/12345/ADTECH;'); + it('should return url with version 2 of pubapi', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('v=2;'); + }); + + it('should return url with cache busting option', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.match(/misc=\d+/); + }); + + it('should return url with default pageId and sizeId', () => { + let bidRequest = createCustomBidRequest({ + params: { + placement: 1234567, + network: '9599.1' + } }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('/pubapi/3.0/9599.1/1234567/0/0/ADTECH;'); + }); - it('should contain generated alias if alias param is missing', () => { - adapter.callBids(createBidderRequest({ - params: { - placement: 1234567, - network: '9599.1' - } - })); - expect(requests[0].url).to.match(/alias=\w+?;/); + it('should return url with custom pageId and sizeId when options are present', () => { + let bidRequest = createCustomBidRequest({ + params: { + placement: 1234567, + network: '9599.1', + pageId: 1111, + sizeId: 2222 + } }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('/pubapi/3.0/9599.1/1234567/1111/2222/ADTECH;'); + }); - it('should contain alias optional param', () => { - adapter.callBids(createBidderRequest({ - params: { - placement: 1234567, - network: '9599.1', - alias: 'desktop_articlepage_something_box_300_250' - } - })); - expect(requests[0].url).to.contain('alias=desktop_articlepage_something_box_300_250'); + it('should return url with default alias if alias param is missing', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.match(/alias=\w+?;/); + }); + + it('should return url with custom alias if it is present', () => { + let bidRequest = createCustomBidRequest({ + params: { + placement: 1234567, + network: '9599.1', + alias: 'desktop_articlepage_something_box_300_250' + } }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('alias=desktop_articlepage_something_box_300_250'); + }); - it('should not contain bidfloor if bidFloor param is missing', () => { - adapter.callBids(createBidderRequest({ - params: { - placement: 1234567, - network: '9599.1' - } - })); - expect(requests[0].url).not.to.contain('bidfloor='); + it('should return url without bidfloor option if is is missing', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).not.to.contain('bidfloor='); + }); + + it('should return url with bidFloor option if it is present', () => { + let bidRequest = createCustomBidRequest({ + params: { + placement: 1234567, + network: '9599.1', + bidFloor: 0.80 + } }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('bidfloor=0.8'); + }); - it('should contain bidFloor optional param', () => { - adapter.callBids(createBidderRequest({ - params: { - placement: 1234567, - network: '9599.1', - bidFloor: 0.80 + it('should return url with key values if keyValues param is present', () => { + let bidRequest = createCustomBidRequest({ + params: { + placement: 1234567, + network: '9599.1', + keyValues: { + age: 25, + height: 3.42, + test: 'key' } - })); - expect(requests[0].url).to.contain('bidfloor=0.8'); + } }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('kvage=25;kvheight=3.42;kvtest=key'); }); - describe('Nexage api', () => { - let xhr; - let requests; + it('should return request object for One Display when configuration is present', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.method).to.equal('GET'); + expect(request.ttl).to.equal(ONE_DISPLAY_TTL); + }); + }); - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); + describe('One Mobile', () => { + it('should return One Mobile url when One Mobile get params are present', () => { + let bidRequest = createCustomBidRequest({ + params: getNexageGetBidParams() }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(NEXAGE_URL); + }); - afterEach(() => xhr.restore()); + it('should return One Mobile url with different host when host option is present', () => { + let bidParams = Object.assign({ + host: '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?'); + }); - it('requires parameters to be made', () => { - adapter.callBids({}); - expect(requests).to.be.empty; + it('should return One Mobile url when One Mobile and Marketplace params are present', () => { + let bidParams = Object.assign(getNexageGetBidParams(), getMarketplaceBidParams()); + let bidRequest = createCustomBidRequest({ + params: bidParams }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(NEXAGE_URL); + }); - it('should hit the nexage api endpoint with the nexage config', () => { - adapter.callBids(createBidderRequest({ - params: getNexageGetBidParams() - })); + it('should return One Mobile url for onemobile bidder code ' + + 'when One Mobile GET and Marketplace params are present', () => { + let bidParams = Object.assign(getNexageGetBidParams(), getMarketplaceBidParams()); + let bidRequest = createCustomBidRequest({ + bids: [{ + bidder: 'onemobile' + }], + params: bidParams + }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(NEXAGE_URL); + }); - expect(requests[0].url).to.contain(NEXAGE_URL); + it('should not return any url for onemobile bidder code' + + 'when only Marketplace params are present', () => { + let bidRequest = createCustomBidRequest({ + bids: [{ + bidder: 'onemobile' + }], + params: getMarketplaceBidParams() }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request).to.be.empty; + }); - it('should hit the nexage api custom endpoint if specified in the nexage config', () => { - let bidParams = Object.assign({ - host: 'qa-hb.nexage.com' - }, getNexageGetBidParams()); - - adapter.callBids(createBidderRequest({ - params: bidParams - })); - expect(requests[0].url).to.contain('qa-hb.nexage.com/bidRequest?'); + it('should return One Mobile url with required params - dcn & pos', () => { + let bidRequest = createCustomBidRequest({ + params: getNexageGetBidParams() }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(NEXAGE_URL + 'dcn=2c9d2b50015c5ce9db6aeeed8b9500d6&pos=header'); + }); - it('should hit nexage api when nexage and marketplace params are present', () => { - let bidParams = Object.assign(getNexageGetBidParams(), getMarketplaceBidParams()); + it('should return One Mobile url with cmd=bid option', () => { + let bidRequest = createCustomBidRequest({ + params: getNexageGetBidParams() + }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('cmd=bid'); + }); - adapter.callBids(createBidderRequest({ - params: bidParams - })); - expect(requests[0].url).to.contain(NEXAGE_URL); + it('should return One Mobile url with generic params if ext option is present', () => { + let bidRequest = createCustomBidRequest({ + params: { + dcn: '54321123', + pos: 'footer-2324', + ext: { + param1: 'val1', + param2: 'val2', + param3: 'val3', + param4: 'val4' + } + } }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('hb.nexage.com/bidRequest?dcn=54321123&pos=footer-2324&cmd=bid' + + '¶m1=val1¶m2=val2¶m3=val3¶m4=val4'); + }); - it('should hit nexage api via onemobile bidder code when nexage and marketplace params are present', () => { - let bidParams = Object.assign(getNexageGetBidParams(), getMarketplaceBidParams()); + it('should return request object for One Mobile POST endpoint when POST configuration is present', () => { + let bidConfig = getNexagePostBidParams(); + let bidRequest = createCustomBidRequest({ + params: bidConfig + }); + + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(NEXAGE_URL); + expect(request.method).to.equal('POST'); + expect(request.ttl).to.equal(ONE_MOBILE_TTL); + expect(request.data).to.deep.equal(bidConfig); + expect(request.options).to.deep.equal({ + contentType: 'application/json', + customHeaders: { + 'x-openrtb-version': '2.2' + } + }); + }); - adapter.callBids(createBidderRequest({ - bids: [{ - bidder: 'onemobile' - }], - params: bidParams - })); - expect(requests[0].url).to.contain(NEXAGE_URL); + it('should not return request object for One Mobile POST endpoint' + + 'if required parameterers are missed', () => { + let bidRequest = createCustomBidRequest({ + params: { + imp: [] + } }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request).to.be.empty; + }); + }); + }); - it('should not resolve endpoint for onemobile bidder code when only Marketplace params are present', () => { - adapter.callBids(createBidderRequest({ - bids: [{ - bidder: 'onemobile' - }], - params: getMarketplaceBidParams() - })); + describe('getUserSyncs()', () => { + let bidResponse; + let bidRequest; - expect(requests.length).to.equal(0); - }); + beforeEach(() => { + $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = false; + config.setConfig({ + aol: { + userSyncOn: 'bidResponse' + }, + }); + bidResponse = getDefaultBidResponse(); + bidResponse.ext = { + pixels: getPixels() + }; + }); - it('should contain required params - dcn & pos', () => { - adapter.callBids(createBidderRequest({ - params: getNexageGetBidParams() - })); + it('should return user syncs only if userSyncOn equals to "bidResponse"', () => { + let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest); - expect(requests[0].url).to.contain(NEXAGE_URL + 'dcn=2c9d2b50015c5ce9db6aeeed8b9500d6&pos=header'); - }); + 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 contain cmd=bid by default', () => { - adapter.callBids(createBidderRequest({ - params: { - dcn: '54321123', - pos: 'footer-2324' - } - })); - expect(requests[0].url).to.contain('hb.nexage.com/bidRequest?dcn=54321123&pos=footer-2324&cmd=bid'); - }); + it('should not return user syncs if it has already been returned', () => { + $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = true; - it('should contain optional parameters if they are set', () => { - adapter.callBids(createBidderRequest({ - params: { - dcn: '54321123', - pos: 'footer-2324', - ext: { - param1: 'val1', - param2: 'val2', - param3: 'val3', - param4: 'val4' - } - } - })); - expect(requests[0].url).to.contain('hb.nexage.com/bidRequest?dcn=54321123&pos=footer-2324&cmd=bid' + - '¶m1=val1¶m2=val2¶m3=val3¶m4=val4'); - }); + let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest); - it('should hit the nexage api endpoint with post data with the openrtb config', () => { - let bidConfig = getNexagePostBidParams(); + expect($$PREBID_GLOBAL$$.aolGlobals.pixelsDropped).to.be.true; + expect(userSyncs).to.deep.equal([]); + }); - adapter.callBids(createBidderRequest({ - params: bidConfig - })); - expect(requests[0].url).to.contain(NEXAGE_URL); - expect(requests[0].requestBody).to.deep.equal(bidConfig); - expect(requests[0].requestHeaders).to.have.property('x-openrtb-version'); - }); + it('should not return user syncs if pixels are not present', () => { + bidResponse.ext.pixels = null; - it('should not hit the nexage api endpoint with post data with the openrtb config' + - ' if a required parameter is missing', () => { - let bidConfig = getNexagePostBidParams(); + let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest); - bidConfig.imp[0].id = null; - adapter.callBids(createBidderRequest({ - params: bidConfig - })); - expect(requests).to.be.empty; - }) - ; - }); + expect($$PREBID_GLOBAL$$.aolGlobals.pixelsDropped).to.be.false; + expect(userSyncs).to.deep.equal([]); }); + }); - describe('bid response', () => { - let server; + describe('formatPixels()', () => { + it('should return pixels wrapped for dropping them once and within nested frames ', () => { + let pixels = ''; + let formattedPixels = spec.formatPixels(pixels); + + expect(formattedPixels).to.equal( + ''); + }); + }); - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); - }); + describe('isOneMobileBidder()', () => { + it('should return false when when bidderCode is not present', () => { + expect(spec.isOneMobileBidder(null)).to.be.false; + }); - afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); - }); + it('should return false for unknown bidder code', () => { + expect(spec.isOneMobileBidder('unknownBidder')).to.be.false; + }); - it('should be added to bidmanager if returned from pubapi', () => { - server.respondWith(JSON.stringify(getDefaultBidResponse())); - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - }); + it('should return true for aol bidder code', () => { + expect(spec.isOneMobileBidder('aol')).to.be.true; + }); - it('should be added to bidmanager if returned from nexage GET bid request', () => { - server.respondWith(JSON.stringify(getDefaultBidResponse())); - adapter.callBids(createBidderRequest({ - params: { - dcn: '54321123', - pos: 'footer-2324' - } - })); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - }); + it('should return true for one mobile bidder code', () => { + expect(spec.isOneMobileBidder('onemobile')).to.be.true; + }); + }); - it('should be added to bidmanager if returned from nexage POST bid request', () => { - server.respondWith(JSON.stringify(getDefaultBidResponse())); - adapter.callBids(createBidderRequest({ - params: { - id: 'id-1', - imp: [{ - id: 'id-2', - banner: { - w: '100', - h: '100' - }, - tagid: 'header1' - }] - } - })); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - var bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - }); - - it('should be added to bidmanager with correct bidderCode', () => { - server.respondWith(JSON.stringify(getDefaultBidResponse())); - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(bidmanager.addBidResponse.firstCall.args[1]).to.have.property('bidderCode', 'aol'); - }); - - it('should have adId matching the bidId from related bid request', () => { - server.respondWith(JSON.stringify(getDefaultBidResponse())); - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(bidmanager.addBidResponse.firstCall.args[1]) - .to.have.property('adId', '84ab500420319d'); - }); - - it('should be added to bidmanager as invalid in case of empty response', () => { - server.respondWith(''); - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(2); - }); - - it('should be added to bidmanager as invalid in case of invalid JSON response', () => { - server.respondWith('{foo:{bar:{baz:'); - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(2); - }); - - it('should be added to bidmanager as invalid in case of no bid data', () => { - let bidResponse = getDefaultBidResponse(); - bidResponse.seatbid = []; - server.respondWith(JSON.stringify(bidResponse)); - - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(2); - }); - - it('should have adId matching the bidId from bid request in case of no bid data', () => { - let bidResponse = getDefaultBidResponse(); - bidResponse.seatbid = []; - server.respondWith(JSON.stringify(bidResponse)); - - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(bidmanager.addBidResponse.firstCall.args[1]) - .to.have.property('adId', '84ab500420319d'); - }); - - it('should be added to bidmanager as invalid in case of empty price', () => { - let bidResponse = getDefaultBidResponse(); - bidResponse.seatbid[0].bid[0].price = undefined; - - server.respondWith(JSON.stringify(bidResponse)); - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(2); - }); - - it('should be added to bidmanager with attributes from pubapi response', () => { - let bidResponse = getDefaultBidResponse(); - bidResponse.seatbid[0].bid[0].crid = '12345'; - - server.respondWith(JSON.stringify(bidResponse)); - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - let addedBidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(addedBidResponse.ad).to.equal(''); - expect(addedBidResponse.cpm).to.equal(0.09); - expect(addedBidResponse.width).to.equal(728); - expect(addedBidResponse.height).to.equal(90); - expect(addedBidResponse.creativeId).to.equal('12345'); - expect(addedBidResponse.pubapiId).to.equal('245730051428950632'); - }); - - it('should be added to bidmanager including pixels from pubapi response', () => { - let bidResponse = getDefaultBidResponse(); - bidResponse.ext = { - pixels: '' - }; - - server.respondWith(JSON.stringify(bidResponse)); - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - let addedBidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(addedBidResponse.ad).to.equal( - '' + - '' - ); - }); - - it('should be added to bidmanager including dealid from pubapi response', () => { - let bidResponse = getDefaultBidResponse(); - bidResponse.seatbid[0].bid[0].dealid = '12345'; - - server.respondWith(JSON.stringify(bidResponse)); - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - let addedBidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(addedBidResponse.dealId).to.equal('12345'); - }); - - it('should be added to bidmanager including encrypted price from pubapi response', () => { - let bidResponse = getDefaultBidResponse(); - bidResponse.seatbid[0].bid[0].ext.encp = 'a9334987'; - server.respondWith(JSON.stringify(bidResponse)); - - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - let addedBidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(addedBidResponse.cpm).to.equal('a9334987'); - }); - - it('should not render pixels on pubapi response when no parameter is set', () => { - let bidResponse = getDefaultBidResponse(); - bidResponse.ext = { - pixels: '' - }; - server.respondWith(JSON.stringify(bidResponse)); - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(document.body.querySelectorAll('iframe[src="pixels.org"]').length).to.equal(0); - }); - - it('should render pixels from pubapi response when param userSyncOn is set with \'bidResponse\'', () => { - let bidResponse = getDefaultBidResponse(); - bidResponse.ext = { - pixels: '' - }; - - server.respondWith(JSON.stringify(bidResponse)); - let bidRequest = getDefaultBidRequest(); - bidRequest.bids[0].params.userSyncOn = 'bidResponse'; - adapter.callBids(bidRequest); - server.respond(); + describe('isConsentRequired()', () => { + it('should return false when consentData object is not present', () => { + expect(spec.isConsentRequired(null)).to.be.false; + }); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; + it('should return false when gdprApplies equals true and consentString is not present', () => { + let consentData = { + consentString: null, + gdprApplies: true + }; - let assertPixelsItem = (pixelsItemSelector) => { - let pixelsItem = document.body.querySelectorAll(pixelsItemSelector)[0]; + expect(spec.isConsentRequired(consentData)).to.be.false; + }); - expect(pixelsItem.width).to.equal('1'); - expect(pixelsItem.height).to.equal('1'); - expect(pixelsItem.style.display).to.equal('none'); - }; + it('should return false when consentString is present and gdprApplies equals false', () => { + let consentData = { + consentString: 'consent-string', + gdprApplies: false + }; - assertPixelsItem('iframe[src="pixels.org"]'); - assertPixelsItem('iframe[src="pixels1.org"]'); - expect($$PREBID_GLOBAL$$.aolGlobals.pixelsDropped).to.be.true; - }); + expect(spec.isConsentRequired(consentData)).to.be.false; + }); - it('should not render pixels if it was rendered before', () => { - $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = true; - let bidResponse = getDefaultBidResponse(); - bidResponse.ext = { - pixels: '' - }; - server.respondWith(JSON.stringify(bidResponse)); + it('should return true when consentString is present and gdprApplies equals true', () => { + let consentData = { + consentString: 'consent-string', + gdprApplies: true + }; - let bidRequest = getDefaultBidRequest(); - bidRequest.bids[0].params.userSyncOn = 'bidResponse'; - adapter.callBids(bidRequest); - server.respond(); + expect(spec.isConsentRequired(consentData)).to.be.true; + }); + }); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; + describe('formatMarketplaceConsentData()', () => { + let consentRequiredStub; - let assertPixelsItem = (pixelsItemSelector) => { - let pixelsItems = document.body.querySelectorAll(pixelsItemSelector); + beforeEach(() => { + consentRequiredStub = sinon.stub(spec, 'isConsentRequired'); + }); - expect(pixelsItems.length).to.equal(0); - }; + afterEach(() => { + consentRequiredStub.restore(); + }); + + it('should return empty string when consent is not required', () => { + consentRequiredStub.returns(false); + expect(spec.formatMarketplaceConsentData()).to.be.equal(''); + }); - assertPixelsItem('iframe[src="test.com"]'); - assertPixelsItem('iframe[src="test2.com"]'); + it('should return formatted consent data when consent is required', () => { + consentRequiredStub.returns(true); + let formattedConsentData = spec.formatMarketplaceConsentData({ + consentString: 'test-consent' }); + expect(formattedConsentData).to.be.equal(';euconsent=test-consent;gdpr=1'); }); + }); - describe('when bidCpmAdjustment is set', () => { - let bidderSettingsBackup; - let server; + describe('formatOneMobileDynamicParams()', () => { + let consentRequiredStub; + let secureProtocolStub; - beforeEach(() => { - bidderSettingsBackup = $$PREBID_GLOBAL$$.bidderSettings; - server = sinon.fakeServer.create(); - }); + beforeEach(() => { + consentRequiredStub = sinon.stub(spec, 'isConsentRequired'); + secureProtocolStub = sinon.stub(spec, 'isSecureProtocol'); + }); - afterEach(() => { - $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsBackup; - server.restore(); - if (utils.logWarn.restore) { - utils.logWarn.restore(); - } - }); + afterEach(() => { + consentRequiredStub.restore(); + secureProtocolStub.restore(); + }); - it('should show warning in the console', function() { - sinon.spy(utils, 'logWarn'); - server.respondWith(JSON.stringify(getDefaultBidResponse())); - $$PREBID_GLOBAL$$.bidderSettings = { - aol: { - bidCpmAdjustment: function() {} - } - }; - adapter.callBids(getDefaultBidRequest()); - server.respond(); - expect(utils.logWarn.calledOnce).to.be.true; - }); + it('should return empty string when params are not present', () => { + expect(spec.formatOneMobileDynamicParams()).to.be.equal(''); + }); + + it('should return formatted params when params are present', () => { + let params = { + param1: 'val1', + param2: 'val2', + param3: 'val3' + }; + expect(spec.formatOneMobileDynamicParams(params)).to.contain('¶m1=val1¶m2=val2¶m3=val3'); + }); + + it('should return formatted gdpr params when isConsentRequired returns true', () => { + let consentData = { + consentString: 'test-consent' + }; + consentRequiredStub.returns(true); + expect(spec.formatOneMobileDynamicParams({}, consentData)).to.be.equal('&euconsent=test-consent&gdpr=1'); + }); + + it('should return formatted secure param when isSecureProtocol returns true', () => { + secureProtocolStub.returns(true); + expect(spec.formatOneMobileDynamicParams()).to.be.equal('&secure=1'); }); }); }); diff --git a/test/spec/modules/appnexusAstBidAdapter_spec.js b/test/spec/modules/appnexusAstBidAdapter_spec.js deleted file mode 100644 index d07ee6df543..00000000000 --- a/test/spec/modules/appnexusAstBidAdapter_spec.js +++ /dev/null @@ -1,386 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/appnexusAstBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; - -const ENDPOINT = '//ib.adnxs.com/ut/v3/prebid'; - -describe('AppNexusAdapter', () => { - 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', () => { - let bid = { - 'bidder': 'appnexusAst', - 'params': { - 'placementId': '10433394' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', () => { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return true when required params found', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'member': '1234', - 'invCode': 'ABCD' - }; - - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'placementId': 0 - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', () => { - let bidRequests = [ - { - 'bidder': 'appnexusAst', - 'params': { - 'placementId': '10433394' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - it('should add source and verison to the tag', () => { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.sdk).to.exist; - expect(payload.sdk).to.deep.equal({ - source: 'pbjs', - version: '$prebid.version$' - }); - }); - - it('sends bid request to ENDPOINT via POST', () => { - const request = spec.buildRequests(bidRequests); - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('POST'); - }); - - it('should attach valid video params to the tag', () => { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - video: { - id: 123, - minduration: 100, - foobar: 'invalid' - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - id: 123, - minduration: 100 - }); - }); - - it('should attach valid user params to the tag', () => { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - user: { - external_uid: '123', - foobar: 'invalid' - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.user).to.exist; - expect(payload.user).to.deep.equal({ - external_uid: '123', - }); - }); - - it('should attache native params to the request', () => { - 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} - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - 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('sets minimum native asset params when not provided on adunit', () => { - let bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'native', - nativeParams: { - image: {required: true}, - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.tags[0].native.layouts[0]).to.deep.equal({ - main_image: {required: true, sizes: [{}] }, - }); - }); - - it('does not overwrite native ad unit params with mimimum params', () => { - let bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'native', - nativeParams: { - image: { - aspect_ratios: [{ - min_width: 100, - ratio_width: 2, - ratio_height: 3, - }] - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - 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, - }] - }, - }); - }); - - it('should convert keyword params to proper form and attaches to request', () => { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - keywords: { - single: 'val', - singleArr: ['val'], - singleArrNum: [5], - multiValMixed: ['value1', 2, 'value3'], - singleValNum: 123, - badValue: {'foo': 'bar'} // should be dropped - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.tags[0].keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'singleArrNum', - 'value': ['5'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'singleValNum', - 'value': ['123'] - }]); - }); - }) - - describe('interpretResponse', () => { - let response = { - 'version': '3.0.0', - 'tags': [ - { - 'uuid': '3db3773286ee59', - 'tag_id': 10433394, - 'auction_id': '4534722592064951574', - 'nobid': false, - 'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad', - 'timeout_ms': 10000, - 'ad_profile_id': 27079, - 'ads': [ - { - 'content_source': 'rtb', - 'ad_type': 'banner', - 'buyer_member_id': 958, - 'creative_id': 29681110, - 'media_type_id': 1, - 'media_subtype_id': 1, - 'cpm': 0.5, - 'cpm_publisher_currency': 0.5, - 'publisher_currency_code': '$', - 'client_initiated_ad_counting': true, - 'rtb': { - 'banner': { - 'content': '', - 'width': 300, - 'height': 250 - }, - 'trackers': [ - { - 'impression_urls': [ - 'http://lax1-ib.adnxs.com/impression' - ], - 'video_events': {} - } - ] - } - } - ] - } - ] - }; - - it('should get correct bid response', () => { - let expectedResponse = [ - { - 'requestId': '3db3773286ee59', - 'cpm': 0.5, - 'creative_id': 29681110, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '', - 'mediaType': 'banner' - } - ]; - let bidderRequest; - - let result = spec.interpretResponse(response, {bidderRequest}); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); - }); - - it('handles nobid responses', () => { - let response = { - 'version': '0.0.1', - 'tags': [{ - 'uuid': '84ab500420319d', - 'tag_id': 5976557, - 'auction_id': '297492697822162468', - 'nobid': true - }] - }; - let bidderRequest; - - let result = spec.interpretResponse(response, {bidderRequest}); - expect(result.length).to.equal(0); - }); - - it('handles non-banner media responses', () => { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'rtb': { - 'video': { - 'content': '' - } - } - }] - }] - }; - let bidderRequest; - - let result = spec.interpretResponse(response, {bidderRequest}); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0]).to.have.property('descriptionUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); - - it('handles native responses', () => { - let response1 = Object.assign({}, response); - response1.tags[0].ads[0].ad_type = 'native'; - response1.tags[0].ads[0].rtb.native = { - 'title': 'Native Creative', - 'desc': 'Cool description great stuff', - 'ctatext': 'Do it', - 'sponsored': 'AppNexus', - 'icon': { - 'width': 0, - 'height': 0, - 'url': 'http://cdn.adnxs.com/icon.png' - }, - 'main_img': { - 'width': 2352, - 'height': 1516, - 'url': 'http://cdn.adnxs.com/img.png' - }, - 'link': { - 'url': 'https://www.appnexus.com', - 'fallback_url': '', - 'click_trackers': ['http://nym1-ib.adnxs.com/click'] - }, - 'impression_trackers': ['http://example.com'], - }; - let bidderRequest; - - let result = spec.interpretResponse(response1, {bidderRequest}); - expect(result[0].native.title).to.equal('Native Creative'); - expect(result[0].native.body).to.equal('Cool description great stuff'); - expect(result[0].native.cta).to.equal('Do it'); - expect(result[0].native.image).to.equal('http://cdn.adnxs.com/img.png'); - }); - }); -}); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 96916f3fa35..abfd50d1746 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1,52 +1,495 @@ -import {expect} from 'chai'; -import Adapter from '../../../modules/appnexusBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import adLoader from '../../../src/adloader'; - -describe('AppNexus Adapter', () => { - let adapter; - - const REQUEST = { - 'bidderCode': 'appnexus', - 'requestId': 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', - 'bidderRequestId': '7101db09af0db2', - 'bids': [ +import { expect } from 'chai'; +import { spec } from 'modules/appnexusBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { deepClone } from 'src/utils'; + +const ENDPOINT = '//ib.adnxs.com/ut/v3/prebid'; + +describe('AppNexusAdapter', () => { + 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', () => { + let bid = { + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'member': '1234', + 'invCode': 'ABCD' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ { 'bidder': 'appnexus', 'params': { - 'placementId': '4799418', - 'trafficSourceCode': 'source' + 'placementId': '10433394' }, - 'placementCode': '/19968336/header-bid-tag1', - 'sizes': [ - [728, 90], - [970, 90] - ], - 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2', - 'requestId': 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6' + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', } - ], - 'start': 1469479810130 - }; - - let sandbox; - let adLoaderStub; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(bidManager, 'addBidResponse'); - adLoaderStub = sandbox.stub(adLoader, 'loadScript'); - }); + ]; - afterEach(() => { - sandbox.restore(); - }); + it('should parse out private sizes', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + privateSizes: [300, 250] + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].private_sizes).to.exist; + expect(payload.tags[0].private_sizes).to.deep.equal([{width: 300, height: 250}]); + }); + + it('should add source and verison to the tag', () => { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.sdk).to.exist; + expect(payload.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + }); + + it('should populate the ad_types array on all requests', () => { + ['banner', 'video', 'native'].forEach(type => { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes[type] = {}; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal([type]); + }); + }); + + it('should populate the ad_types array on outstream requests', () => { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = {context: 'outstream'}; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal(['video']); + }); + + it('sends bid request to ENDPOINT via POST', () => { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should attach valid video params to the tag', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + video: { + id: 123, + minduration: 100, + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + id: 123, + minduration: 100 + }); + }); + + it('should attach valid user params to the tag', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + user: { + external_uid: '123', + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.user).to.exist; + expect(payload.user).to.deep.equal({ + external_uid: '123', + }); + }); + + it('should attach native params to the request', () => { + 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} + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + 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('sets minimum native asset params when not provided on adunit', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + image: {required: true}, + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].native.layouts[0]).to.deep.equal({ + main_image: {required: true, sizes: [{}]}, + }); + }); + + it('does not overwrite native ad unit params with mimimum params', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + image: { + aspect_ratios: [{ + min_width: 100, + ratio_width: 2, + ratio_height: 3, + }] + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + 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, + }] + }, + }); + }); + + it('should convert keyword params to proper form and attaches to request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + keywords: { + single: 'val', + singleArr: ['val'], + singleArrNum: [5], + multiValMixed: ['value1', 2, 'value3'], + singleValNum: 123, + badValue: {'foo': 'bar'} // should be dropped + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'singleArrNum', + 'value': ['5'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'singleValNum', + 'value': ['123'] + }]); + }); + + it('should add payment rules to the request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + usePaymentRule: true + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].use_pmt_rule).to.equal(true); + }); + + it('should add gdpr consent information to the request', () => { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'appnexus', + '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_consent).to.exist; + expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString); + expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; + }); + }) + + describe('interpretResponse', () => { + let response = { + 'version': '3.0.0', + 'tags': [ + { + 'uuid': '3db3773286ee59', + 'tag_id': 10433394, + 'auction_id': '4534722592064951574', + 'nobid': false, + 'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad', + 'timeout_ms': 10000, + 'ad_profile_id': 27079, + 'ads': [ + { + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 958, + 'creative_id': 29681110, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 0.5, + 'cpm_publisher_currency': 0.5, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'rtb': { + 'banner': { + 'content': '', + 'width': 300, + 'height': 250 + }, + 'trackers': [ + { + 'impression_urls': [ + 'http://lax1-ib.adnxs.com/impression' + ], + 'video_events': {} + } + ] + } + } + ] + } + ] + }; + + it('should get correct bid response', () => { + let expectedResponse = [ + { + 'requestId': '3db3773286ee59', + 'cpm': 0.5, + 'creativeId': 29681110, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'netRevenue': true, + 'appnexus': { + 'buyerMemberId': 958 + } + } + ]; + let bidderRequest; + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', () => { + let response = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '84ab500420319d', + 'tag_id': 5976557, + 'auction_id': '297492697822162468', + 'nobid': true + }] + }; + let bidderRequest; + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result.length).to.equal(0); + }); + + it('handles non-banner media responses', () => { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + } + }] + }] + }; + let bidderRequest; + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('handles native responses', () => { + 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', + 'ctatext': 'Do it', + 'sponsored': 'AppNexus', + 'icon': { + 'width': 0, + 'height': 0, + 'url': 'http://cdn.adnxs.com/icon.png' + }, + 'main_img': { + 'width': 2352, + 'height': 1516, + 'url': 'http://cdn.adnxs.com/img.png' + }, + 'link': { + 'url': 'https://www.appnexus.com', + 'fallback_url': '', + 'click_trackers': ['http://nym1-ib.adnxs.com/click'] + }, + 'impression_trackers': ['http://example.com'], + }; + let bidderRequest; + + let result = spec.interpretResponse({ body: response1 }, {bidderRequest}); + expect(result[0].native.title).to.equal('Native Creative'); + expect(result[0].native.body).to.equal('Cool description great stuff'); + expect(result[0].native.cta).to.equal('Do it'); + expect(result[0].native.image.url).to.equal('http://cdn.adnxs.com/img.png'); + }); + + it('supports configuring outstream renderers', () => { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + + const bidderRequest = { + bids: [{ + renderer: { + options: { + adText: 'configured' + } + } + }] + }; - describe('callBids', () => { - it('should contain traffic_source_code', () => { - adapter = new Adapter(); - adapter.callBids(REQUEST); - expect(adLoaderStub.getCall(0).args[0]).to.contain('traffic_source_code=source'); + const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); }); }); }); diff --git a/test/spec/modules/arteebeeBidAdapter_spec.js b/test/spec/modules/arteebeeBidAdapter_spec.js new file mode 100644 index 00000000000..fe5bbf7ff25 --- /dev/null +++ b/test/spec/modules/arteebeeBidAdapter_spec.js @@ -0,0 +1,128 @@ +import {expect} from 'chai'; +import {spec} from 'modules/arteebeeBidAdapter'; + +describe('Arteebee adapater', () => { + describe('Test validate req', () => { + it('should accept minimum valid bid', () => { + let bid = { + bidder: 'arteebee', + params: { + pub: 'prebidtest', + source: 'prebidtest' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(true); + }); + + it('should reject missing pub', () => { + let bid = { + bidder: 'arteebee', + params: { + source: 'prebidtest' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + + it('should reject missing source', () => { + let bid = { + bidder: 'arteebee', + params: { + pub: 'prebidtest' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + }); + + describe('Test build request', () => { + it('minimum request', () => { + let bid = { + bidder: 'arteebee', + params: { + pub: 'prebidtest', + source: 'prebidtest' + }, + sizes: [[300, 250]] + }; + + const req = JSON.parse(spec.buildRequests([bid])[0].data); + + expect(req).to.not.have.property('reg'); + expect(req).to.not.have.property('test'); + expect(req.imp[0]).to.not.have.property('secure'); + }); + + it('make test request', () => { + let bid = { + bidder: 'arteebee', + params: { + pub: 'prebidtest', + source: 'prebidtest', + test: true + }, + sizes: [[300, 250]] + }; + + const req = JSON.parse(spec.buildRequests([bid])[0].data); + + expect(req).to.not.have.property('reg'); + expect(req).to.have.property('test', 1); + expect(req.imp[0]).to.not.have.property('secure'); + }); + + it('test coppa', () => { + let bid = { + bidder: 'arteebee', + params: { + pub: 'prebidtest', + source: 'prebidtest', + coppa: true + }, + sizes: [[300, 250]] + }; + + const req = JSON.parse(spec.buildRequests([bid])[0].data); + + expect(req.regs).to.have.property('coppa', 1); + expect(req).to.not.have.property('test'); + expect(req.imp[0]).to.not.have.property('secure'); + }); + }); + + describe('Test interpret response', () => { + it('General banner response', () => { + let resp = spec.interpretResponse({ + body: { + id: 'abcd', + seatbid: [{ + bid: [{ + id: 'abcd', + impid: 'banner-bid', + price: 0.3, + adm: 'hello', + crid: 'efgh', + w: 300, + h: 250, + exp: 5 + }] + }] + } + }, null)[0]; + + expect(resp).to.have.property('requestId', 'banner-bid'); + expect(resp).to.have.property('cpm', 0.3); + expect(resp).to.have.property('width', 300); + expect(resp).to.have.property('height', 250); + expect(resp).to.have.property('creativeId', 'efgh'); + expect(resp).to.have.property('ttl', 5); + expect(resp).to.have.property('ad', 'hello'); + }); + }); +}); diff --git a/test/spec/modules/atomxBidAdapter_spec.js b/test/spec/modules/atomxBidAdapter_spec.js index 646061912e7..fdbb01a1838 100644 --- a/test/spec/modules/atomxBidAdapter_spec.js +++ b/test/spec/modules/atomxBidAdapter_spec.js @@ -1,150 +1,119 @@ -var chai = require('chai'); -var Adapter = require('modules/atomxBidAdapter')(); -var Ajax = require('src/ajax'); -var adLoader = require('src/adloader'); -var bidmanager = require('src/bidmanager.js'); -var CONSTANTS = require('src/constants.json'); +import { expect } from 'chai'; +import { spec } from 'modules/atomxBidAdapter'; -describe('Atomx adapter', function () { - var validData_1 = { - bids: [ - { +describe('atomxAdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with id param', () => { + expect(spec.isBidRequestValid({ bidder: 'atomx', - bidId: 'bid_id', - params: {id: 1234}, - placementCode: 'ad-unit-1', - sizes: [[300, 250], [800, 600]] - } - ] - }; - var validData_2 = { - bids: [ - { - bidder: 'adtomx', - bidId: 'bid_id', - params: {id: 5678}, - placementCode: 'ad-unit-1', - sizes: [300, 250] - } - ] - }; + params: { + id: 1234, + }, + })).to.equal(true); + }); - var invalidData = { - bids: [ - { + it('bidRequest with no id param', () => { + expect(spec.isBidRequestValid({ bidder: 'atomx', - bidId: 'bid_id', - params: {}, - placementCode: 'ad-unit-1', - sizes: [[300, 250]] - } - ] - }; - - var responseWithAd = JSON.stringify({ - 'cpm': 2.2, - 'url': 'http://p.ato.mx/placement?id=1234', - 'width': 300, - 'height': 250, - 'code': 'ad-unit-1' - }); - var responseWithoutAd = JSON.stringify({ - 'cpm': 0, - 'url': 'http://p.ato.mx/placement?id=1234', - 'width': 300, - 'height': 250, - 'code': 'ad-unit-1' + params: { + }, + })).to.equal(false); + }); }); - var responseEmpty = ''; - var validJsonParams = { - id: '1234', - prebid: 'ad-unit-1', - size: '300x250' - }; + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'atomx', + 'params': { + 'id': '123' + }, + 'adUnitCode': 'aaa', + 'transactionId': '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', + 'sizes': [300, 250], + 'bidId': '1abgs362e0x48a8', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': '5c66da22-426a-4bac-b153-77360bef5337' + }, + { + 'bidder': 'atomx', + 'params': { + 'id': '456', + }, + 'adUnitCode': 'bbb', + 'transactionId': '193995b4-7122-4739-959b-2463282a138b', + 'sizes': [[800, 600]], + 'bidId': '22aidtbx5eabd9', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' + }]; - describe('loads the tag code', function() { - var stubLoadScript = sinon.stub(adLoader, 'loadScript'); - Adapter.callBids(validData_1); - sinon.assert.calledOnce(stubLoadScript); - let url = stubLoadScript.firstCall.args[0]; - let callback = stubLoadScript.firstCall.args[1]; - expect(url).to.equal('http://s.ato.mx/b.js'); - expect(callback).to.be.a('function'); - }); - describe('bid request with valid data', function () { - var stubAjax; - beforeEach(function () { - window.atomx_prebid = function() { - return '/placement'; - }; - stubAjax = sinon.stub(Ajax, 'ajax'); - }); - afterEach(function () { - stubAjax.restore(); - }); - it('bid request should be called. sizes style -> [[],[]]', function () { - Adapter.callBids(validData_1); - sinon.assert.calledTwice(stubAjax); + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('GET'); + }); }); - it('bid request should be called. sizes style -> []', function () { - Adapter.callBids(validData_2); - sinon.assert.calledOnce(stubAjax); - }); - it('ajax params should be matched', function () { - Adapter.callBids(validData_1); - sinon.assert.calledWith(stubAjax, sinon.match('/placement', function () { - }, validJsonParams, {method: 'GET'})); - }); - }); - describe('bid request with invalid data', function () { - var addBidResponse, stubAjax; - beforeEach(function () { - window.atomx_prebid = function() { - return '/placement'; - }; - addBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - stubAjax = sinon.stub(Ajax, 'ajax'); - }); - afterEach(function () { - addBidResponse.restore(); - stubAjax.restore(); - }); - it('ajax shouldn\'t be called', function () { - Adapter.callBids(invalidData); - sinon.assert.notCalled(stubAjax); + + it('bidRequest url', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.url).to.match(new RegExp('p\\.ato\\.mx/placement')); + }); }); - it('bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID + '"', function () { - Adapter.callBids(invalidData); - expect(addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(addBidResponse.firstCall.args[1].bidderCode).to.equal('atomx'); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].data.id).to.equal('123'); + expect(requests[0].data.size).to.equal('300x250'); + expect(requests[0].data.prebid).to.equal('1abgs362e0x48a8'); + expect(requests[1].data.id).to.equal('456'); + expect(requests[1].data.size).to.equal('800x600'); + expect(requests[1].data.prebid).to.equal('22aidtbx5eabd9'); }); }); - describe('bid response', function () { - var addBidResponse; - beforeEach(function () { - addBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - }); - afterEach(function () { - addBidResponse.restore(); - }); - it('with ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.GOOD + '"', function () { - Adapter.responseCallback(validData_1.bids[0], responseWithAd); - var arg = addBidResponse.firstCall.args[1]; - expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(arg.bidderCode).to.equal('atomx'); - }); - it('without ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID, function () { - Adapter.responseCallback(validData_1.bids[0], responseWithoutAd); - var arg = addBidResponse.firstCall.args[1]; - expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(arg.bidderCode).to.equal('atomx'); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'GET', + 'url': 'https://p.ato.mx/placement', + 'data': { + 'v': 12, + 'id': '123', + 'size': '300x250', + 'prebid': '22aidtbx5eabd9', + 'b': 0, + 'h': '7t3y9', + 'type': 'javascript', + 'screen': '800x600x32', + 'timezone': 0, + 'domain': 'https://example.com', + 'r': '', + } + }; + + const bidResponse = { + body: { + 'cpm': 0.00009, + 'width': 300, + 'height': 250, + 'url': 'http://atomx.com', + 'creative_id': 456, + 'code': '22aidtbx5eabd9', + }, + headers: {} + }; + + it('result is correct', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + expect(result[0].requestId).to.equal('22aidtbx5eabd9'); + expect(result[0].cpm).to.equal(0.00009 * 1000); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(456); + expect(result[0].currency).to.equal('USD'); + expect(result[0].ttl).to.equal(60); + expect(result[0].adUrl).to.equal('http://atomx.com'); }); - it('empty. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID, function () { - Adapter.responseCallback(validData_1.bids[0], responseEmpty); - var arg = addBidResponse.firstCall.args[1]; - expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(arg.bidderCode).to.equal('atomx'); - }) }); }); diff --git a/test/spec/modules/audienceNetworkBidAdapter_spec.js b/test/spec/modules/audienceNetworkBidAdapter_spec.js index 3dcd4833871..f9d46e100b1 100644 --- a/test/spec/modules/audienceNetworkBidAdapter_spec.js +++ b/test/spec/modules/audienceNetworkBidAdapter_spec.js @@ -3,542 +3,416 @@ */ import { expect } from 'chai'; -import bidmanager from 'src/bidmanager'; -import { STATUS } from 'src/constants.json'; +import { spec } from 'modules/audienceNetworkBidAdapter'; import * as utils from 'src/utils'; -import AudienceNetwork from 'modules/audienceNetworkBidAdapter'; +const { + code, + supportedMediaTypes, + isBidRequestValid, + buildRequests, + interpretResponse +} = spec; -const bidderCode = 'audienceNetwork'; +const bidder = 'audienceNetwork'; const placementId = 'test-placement-id'; -const placementCode = '/test/placement/code'; const playerwidth = 320; const playerheight = 180; - -/** - * Expect haystack string to contain needle n times. - * @param {String} haystack - * @param {String} needle - * @param {String} [n=1] - * @throws {Error} - */ -const expectToContain = (haystack, needle, n = 1) => - expect(haystack.split(needle)).to.have.lengthOf(n + 1, - `expected ${n} occurrence(s) of '${needle}' in '${haystack}'`); +const requestId = 'test-request-id'; +const pbv = '$prebid.version$'; describe('AudienceNetwork adapter', () => { describe('Public API', () => { - const adapter = new AudienceNetwork(); - it('getBidderCode', () => { - expect(adapter.getBidderCode).to.be.a('function'); - expect(adapter.getBidderCode()).to.equal(bidderCode); + it('code', () => { + expect(code).to.equal(bidder); }); - it('setBidderCode', () => { - expect(adapter.setBidderCode).to.be.a('function'); + it('supportedMediaTypes', () => { + expect(supportedMediaTypes).to.deep.equal(['banner', 'video']); }); - it('callBids', () => { - expect(adapter.setBidderCode).to.be.a('function'); + it('isBidRequestValid', () => { + expect(isBidRequestValid).to.be.a('function'); }); - }); - - describe('callBids parameter parsing', () => { - let xhr; - let requests; - let addBidResponse; - let logError; - - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - xhr.onCreate = request => requests.push(request); - requests = []; - addBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - logError = sinon.stub(utils, 'logError'); + it('buildRequests', () => { + expect(buildRequests).to.be.a('function'); }); - - afterEach(() => { - xhr.restore(); - bidmanager.addBidResponse.restore(); - utils.logError.restore(); + it('interpretResponse', () => { + expect(interpretResponse).to.be.a('function'); }); + }); + describe('isBidRequestValid', () => { it('missing placementId parameter', () => { - // Invalid parameters - const params = { - bidderCode, - bids: [{ - bidder: bidderCode, - sizes: ['native'] - }] - }; - // Request bids - new AudienceNetwork().callBids(params); - // Verify no attempt to fetch response - expect(requests).to.have.lengthOf(0); - // Verify no attempt to add a response as no placement was provided - expect(addBidResponse.calledOnce).to.equal(false); - // Verify attempt to log error - expect(logError.calledOnce).to.equal(true); + expect(isBidRequestValid({ + bidder, + sizes: [[300, 250]] + })).to.equal(false); }); it('invalid sizes parameter', () => { - // Invalid parameters - const params = { - bidderCode, - bids: [{ - bidder: bidderCode, - params: { placementId }, - sizes: ['', undefined, null, '300x100', [300, 100], [300], {}] - }] - }; - // Request bids - new AudienceNetwork().callBids(params); - // Verify no attempt to fetch response - expect(requests).to.have.lengthOf(0); - // Verify attempt to log error - expect(logError.calledOnce).to.equal(true); + expect(isBidRequestValid({ + bidder, + sizes: ['', undefined, null, '300x100', [300, 100], [300], {}], + params: { placementId } + })).to.equal(false); }); - it('filter valid sizes', () => { - // Valid parameters - const params = { - bidderCode, - bids: [{ - bidder: bidderCode, - params: { placementId }, - sizes: [[1, 1], [300, 250]] - }] - }; - // Request bids - new AudienceNetwork().callBids(params); - // Verify attempt to fetch response - expect(requests).to.have.lengthOf(1); - expect(requests[0].method).to.equal('GET'); - expect(requests[0].url) - .to.contain('https://an.facebook.com/v2/placementbid.json?') - .and.to.contain('placementids[]=test-placement-id') - .and.to.contain('adformats[]=300x250') - .and.to.contain('pageurl=http%3A%2F%2F'); - // Verify no attempt to log error - expect(logError.called).to.equal(false); + it('valid when at least one valid size', () => { + expect(isBidRequestValid({ + bidder, + sizes: [[1, 1], [300, 250]], + params: { placementId } + })).to.equal(true); }); it('valid parameters', () => { - const params = { - bidderCode, - bids: [{ - bidder: bidderCode, - params: { placementId }, - sizes: [[300, 250], [320, 50]] - }, - { - bidder: bidderCode, - params: { placementId }, - sizes: [[320, 50], [300, 250]] - }] - }; - // Request bids - new AudienceNetwork().callBids(params); - // Verify attempt to fetch response - expect(requests).to.have.lengthOf(1); - expect(requests[0].method).to.equal('GET'); - expect(requests[0].url) - .to.contain('https://an.facebook.com/v2/placementbid.json?') - .and.to.contain('placementids[]=test-placement-id&placementids[]=test-placement-id') - .and.to.contain('adformats[]=320x50') - .and.to.contain('adformats[]=300x250') - .and.to.contain('pageurl=http%3A%2F%2F'); - // Verify no attempt to log error - expect(logError.called).to.equal(false); + expect(isBidRequestValid({ + bidder, + sizes: [[300, 250], [320, 50]], + params: { placementId } + })).to.equal(true); }); it('fullwidth', () => { - // Valid parameters - const params = { - bidderCode, - bids: [{ - bidder: bidderCode, - params: { - placementId, - format: 'fullwidth' - }, - sizes: [[300, 250]] - }] - }; - // Request bids - new AudienceNetwork().callBids(params); - // Verify attempt to fetch response - expect(requests).to.have.lengthOf(1); - expect(requests[0].method).to.equal('GET'); - expect(requests[0].url) - .to.contain('https://an.facebook.com/v2/placementbid.json?') - .and.to.contain('placementids[]=test-placement-id') - .and.to.contain('adformats[]=fullwidth') - .and.to.contain('pageurl=http%3A%2F%2F'); - // Verify no attempt to log error - expect(logError.called).to.equal(false); + expect(isBidRequestValid({ + bidder, + sizes: [[300, 250], [336, 280]], + params: { + placementId, + format: 'fullwidth' + } + })).to.equal(true); }); it('native', () => { - // Valid parameters - const params = { - bidderCode, - bids: [{ - bidder: bidderCode, - params: { - placementId, - format: 'native' - }, - sizes: [[300, 250]] - }] - }; - // Request bids - new AudienceNetwork().callBids(params); - // Verify attempt to fetch response - expect(requests).to.have.lengthOf(1); - expect(requests[0].method).to.equal('GET'); - expect(requests[0].url) - .to.contain('https://an.facebook.com/v2/placementbid.json?') - .and.to.contain('placementids[]=test-placement-id') - .and.to.contain('adformats[]=native') - .and.to.contain('pageurl=http%3A%2F%2F'); - // Verify no attempt to log error - expect(logError.called).to.equal(false); + expect(isBidRequestValid({ + bidder, + sizes: [[300, 250]], + params: { + placementId, + format: 'native' + } + })).to.equal(true); + }); + + it('native with non-IAB size', () => { + expect(isBidRequestValid({ + bidder, + sizes: [[728, 90]], + params: { + placementId, + format: 'native' + } + })).to.equal(true); }); it('video', () => { - // Valid parameters - const params = { - bidderCode, - bids: [{ - bidder: bidderCode, - params: { - placementId, - format: 'video' - }, - sizes: [[playerwidth, playerheight]] - }] - }; - // Request bids - new AudienceNetwork().callBids(params); - // Verify attempt to fetch response - expect(requests).to.have.lengthOf(1); - expect(requests[0].method).to.equal('GET'); - expect(requests[0].url) - .to.contain('https://an.facebook.com/v2/placementbid.json?') - .and.to.contain('placementids[]=test-placement-id') - .and.to.contain('adformats[]=video') - .and.to.contain('sdk[]=') - .and.to.contain('pageurl=http%3A%2F%2F'); - // Verify no attempt to log error - expect(logError.called).to.equal(false); + expect(isBidRequestValid({ + bidder, + sizes: [[playerwidth, playerheight]], + params: { + placementId, + format: 'video' + } + })).to.equal(true); }); }); - describe('callBids response handling', () => { - let server; - let addBidResponse; - let logError; + describe('buildRequests', () => { + let isSafariBrowserStub; + before(() => { + isSafariBrowserStub = sinon.stub(utils, 'isSafariBrowser'); + }); + + after(() => { + isSafariBrowserStub.restore(); + }); + + it('can build URL for IAB unit', () => { + expect(buildRequests([{ + bidder, + bidId: requestId, + sizes: [[300, 250], [320, 50]], + params: { placementId } + }])).to.deep.equal([{ + adformats: ['300x250'], + method: 'GET', + requestIds: [requestId], + sizes: ['300x250'], + url: 'https://an.facebook.com/v2/placementbid.json', + data: `placementids[]=test-placement-id&adformats[]=300x250&testmode=false&pageurl=&sdk[]=5.5.web&pbv=${pbv}` + }]); + }); + + it('can build URL for video unit', () => { + expect(buildRequests([{ + bidder, + bidId: requestId, + sizes: [[640, 480]], + params: { + placementId, + format: 'video' + } + }])).to.deep.equal([{ + adformats: ['video'], + method: 'GET', + requestIds: [requestId], + sizes: ['640x480'], + url: 'https://an.facebook.com/v2/placementbid.json', + data: `placementids[]=test-placement-id&adformats[]=video&testmode=false&pageurl=&sdk[]=&pbv=${pbv}&playerwidth=640&playerheight=480` + }]); + }); + + it('can build URL for native unit in non-IAB size', () => { + expect(buildRequests([{ + bidder, + bidId: requestId, + sizes: [[728, 90]], + params: { + placementId, + format: 'native' + } + }])).to.deep.equal([{ + adformats: ['native'], + method: 'GET', + requestIds: [requestId], + sizes: ['728x90'], + url: 'https://an.facebook.com/v2/placementbid.json', + data: `placementids[]=test-placement-id&adformats[]=native&testmode=false&pageurl=&sdk[]=5.5.web&pbv=${pbv}` + }]); + }); - beforeEach(() => { - server = sinon.fakeServer.create(); - addBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - logError = sinon.stub(utils, 'logError'); + it('can build URL for fullwidth 300x250 unit', () => { + expect(buildRequests([{ + bidder, + bidId: requestId, + sizes: [[300, 250]], + params: { + placementId, + format: 'fullwidth' + } + }])).to.deep.equal([{ + adformats: ['fullwidth'], + method: 'GET', + requestIds: [requestId], + sizes: ['300x250'], + url: 'https://an.facebook.com/v2/placementbid.json', + data: `placementids[]=test-placement-id&adformats[]=fullwidth&testmode=false&pageurl=&sdk[]=5.5.web&pbv=${pbv}` + }]); }); - afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); - utils.logError.restore(); + it('can build URL on Safari that includes a cachebuster param', () => { + isSafariBrowserStub.returns(true); + expect(buildRequests([{ + bidder, + bidId: requestId, + sizes: [[300, 250]], + params: { placementId } + }])[0].data).to.contain('&cb='); }); + }); + describe('interpretResponse', () => { it('error in response', () => { - // Error response - const error = 'test-error-message'; - server.respondWith(JSON.stringify({ - errors: [error] - })); - // Request bids - new AudienceNetwork().callBids({ - bidderCode, - bids: [{ - bidder: bidderCode, - params: { placementId }, - sizes: [[300, 250]] - }] - }); - server.respond(); - // Verify attempt to call addBidResponse - expect(addBidResponse.calledOnce).to.equal(true); - expect(addBidResponse.args[0]).to.have.lengthOf(2); - expect(addBidResponse.args[0][1].getStatusCode()).to.equal(STATUS.NO_BID); - expect(addBidResponse.args[0][1].bidderCode).to.equal(bidderCode); - // Verify attempt to log error - expect(logError.calledOnce).to.equal(true); - expect(logError.calledWith(error)).to.equal(true); + expect(interpretResponse({ + body: { + errors: ['test-error-message'] + } + }, {})).to.deep.equal([]); }); it('valid native bid in response', () => { - // Valid response - server.respondWith(JSON.stringify({ - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: 'test-bid-id', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] + const [bidResponse] = interpretResponse({ + body: { + errors: [], + bids: { + [placementId]: [{ + placement_id: placementId, + bid_id: 'test-bid-id', + bid_price_cents: 123, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }] + } } - })); - // Request bids - new AudienceNetwork().callBids({ - bidderCode, - bids: [{ - bidder: bidderCode, - placementCode, - params: { - placementId, - format: 'native' - }, - sizes: [[300, 250]] - }] + }, { + adformats: ['native'], + requestIds: [requestId], + sizes: [[300, 250]] }); - server.respond(); - // Verify attempt to call addBidResponse - expect(addBidResponse.calledOnce).to.equal(true); - expect(addBidResponse.args[0]).to.have.lengthOf(2); - expect(addBidResponse.args[0][0]).to.equal(placementCode); - // Verify Prebid attributes in bid response - const bidResponse = addBidResponse.args[0][1]; - expect(bidResponse.getStatusCode()).to.equal(STATUS.GOOD); + expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.bidderCode).to.equal(bidderCode); + expect(bidResponse.requestId).to.equal(requestId); 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') .and.to.contain('getElementsByTagName("style")', 'ad missing native styles') .and.to.contain('
', 'ad missing native container'); - // Verify Audience Network attributes in bid response + expect(bidResponse.creativeId).to.equal(placementId); + expect(bidResponse.netRevenue).to.equal(true); + expect(bidResponse.currency).to.equal('USD'); + expect(bidResponse.hb_bidder).to.equal('fan'); expect(bidResponse.fb_bidid).to.equal('test-bid-id'); expect(bidResponse.fb_format).to.equal('native'); expect(bidResponse.fb_placementid).to.equal(placementId); - // Verify no attempt to log error - expect(logError.called).to.equal(false, 'logError called'); }); it('valid IAB bid in response', () => { - // Valid response - server.respondWith(JSON.stringify({ - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: 'test-bid-id', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] + const [bidResponse] = interpretResponse({ + body: { + errors: [], + bids: { + [placementId]: [{ + placement_id: placementId, + bid_id: 'test-bid-id', + bid_price_cents: 123, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }] + } } - })); - // Request bids - new AudienceNetwork().callBids({ - bidderCode, - bids: [{ - bidder: bidderCode, - placementCode, - params: { placementId }, - sizes: [[300, 250]] - }] + }, { + adformats: ['300x250'], + requestIds: [requestId], + sizes: [[300, 250]] }); - server.respond(); - // Verify attempt to call addBidResponse - expect(addBidResponse.calledOnce).to.equal(true); - expect(addBidResponse.args[0]).to.have.lengthOf(2); - expect(addBidResponse.args[0][0]).to.equal(placementCode); - // Verify bidResponse Object - const bidResponse = addBidResponse.args[0][1]; - expect(bidResponse.getStatusCode()).to.equal(STATUS.GOOD); + expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.bidderCode).to.equal(bidderCode); + expect(bidResponse.requestId).to.equal(requestId); 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') .and.not.to.contain('getElementsByTagName("style")', 'ad should not contain native styles') .and.not.to.contain('
', 'ad should not contain native container'); - // Verify no attempt to log error - expect(logError.called).to.equal(false, 'logError called'); + expect(bidResponse.creativeId).to.equal(placementId); + expect(bidResponse.netRevenue).to.equal(true); + expect(bidResponse.currency).to.equal('USD'); + expect(bidResponse.hb_bidder).to.equal('fan'); + expect(bidResponse.fb_bidid).to.equal('test-bid-id'); + expect(bidResponse.fb_format).to.equal('300x250'); + expect(bidResponse.fb_placementid).to.equal(placementId); }); it('filters invalid slot sizes', () => { - // Valid response - server.respondWith(JSON.stringify({ - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: 'test-bid-id', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] + const [bidResponse] = interpretResponse({ + body: { + errors: [], + bids: { + [placementId]: [{ + placement_id: placementId, + bid_id: 'test-bid-id', + bid_price_cents: 123, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }] + } } - })); - // Request bids - new AudienceNetwork().callBids({ - bidderCode, - bids: [{ - bidder: bidderCode, - placementCode, - params: { placementId }, - sizes: ['350x200'] - }, { - bidder: bidderCode, - placementCode, - params: { placementId }, - sizes: [[300, 250]] - }] + }, { + adformats: ['300x250'], + requestIds: [requestId], + sizes: [[300, 250]] }); - server.respond(); - // Verify attempt to call addBidResponse - expect(addBidResponse.calledOnce).to.equal(true); - expect(addBidResponse.args[0]).to.have.lengthOf(2); - expect(addBidResponse.args[0][0]).to.equal(placementCode); - // Verify bidResponse Object - const bidResponse = addBidResponse.args[0][1]; - expect(bidResponse.getStatusCode()).to.equal(STATUS.GOOD); + expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.bidderCode).to.equal(bidderCode); + expect(bidResponse.requestId).to.equal(requestId); expect(bidResponse.width).to.equal(300); expect(bidResponse.height).to.equal(250); - // Verify no attempt to log error - expect(logError.called).to.equal(false, 'logError called'); + expect(bidResponse.creativeId).to.equal(placementId); + expect(bidResponse.netRevenue).to.equal(true); + expect(bidResponse.currency).to.equal('USD'); + expect(bidResponse.hb_bidder).to.equal('fan'); + expect(bidResponse.fb_bidid).to.equal('test-bid-id'); + expect(bidResponse.fb_format).to.equal('300x250'); + expect(bidResponse.fb_placementid).to.equal(placementId); }); it('valid multiple bids in response', () => { const placementIdNative = 'test-placement-id-native'; const placementIdIab = 'test-placement-id-iab'; - const placementCodeNative = 'test-placement-code-native'; - const placementCodeIab = 'test-placement-code-iab'; - // Valid response - server.respondWith(JSON.stringify({ - errors: [], - bids: { - [placementIdNative]: [{ - placement_id: placementIdNative, - bid_id: 'test-bid-id-native', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }], - [placementIdIab]: [{ - placement_id: placementIdIab, - bid_id: 'test-bid-id-iab', - bid_price_cents: 456, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] + + const [bidResponseNative, bidResponseIab] = interpretResponse({ + body: { + errors: [], + bids: { + [placementIdNative]: [{ + placement_id: placementIdNative, + bid_id: 'test-bid-id-native', + bid_price_cents: 123, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }], + [placementIdIab]: [{ + placement_id: placementIdIab, + bid_id: 'test-bid-id-iab', + bid_price_cents: 456, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }] + } } - })); - // Request bids - new AudienceNetwork().callBids({ - bidderCode, - bids: [{ - bidder: bidderCode, - placementCode: placementCodeNative, - params: { - placementId: placementIdNative, - format: 'native' - }, - sizes: [[300, 250]] - }, { - bidder: bidderCode, - placementCode: placementCodeIab, - params: { placementId: placementIdIab }, - sizes: [[300, 250]] - }] + }, { + adformats: ['native', '300x250'], + requestIds: [requestId, requestId], + sizes: ['300x250', [300, 250]] }); - server.respond(); - // Verify multiple attempts to call addBidResponse - expect(addBidResponse.calledTwice).to.equal(true); - // Verify native - const addBidResponseNativeCall = addBidResponse.args[0]; - expect(addBidResponseNativeCall).to.have.lengthOf(2); - expect(addBidResponseNativeCall[0]).to.equal(placementCodeNative); - expect(addBidResponseNativeCall[1].getStatusCode()).to.equal(STATUS.GOOD); - expect(addBidResponseNativeCall[1].cpm).to.equal(1.23); - expect(addBidResponseNativeCall[1].bidderCode).to.equal(bidderCode); - expect(addBidResponseNativeCall[1].width).to.equal(300); - expect(addBidResponseNativeCall[1].height).to.equal(250); - expect(addBidResponseNativeCall[1].ad).to.contain(`placementid:'${placementIdNative}',format:'native',bidid:'test-bid-id-native'`, 'ad missing parameters'); - // Verify IAB - const addBidResponseIabCall = addBidResponse.args[1]; - expect(addBidResponseIabCall).to.have.lengthOf(2); - expect(addBidResponseIabCall[0]).to.equal(placementCodeIab); - expect(addBidResponseIabCall[1].getStatusCode()).to.equal(STATUS.GOOD); - expect(addBidResponseIabCall[1].cpm).to.equal(4.56); - expect(addBidResponseIabCall[1].bidderCode).to.equal(bidderCode); - expect(addBidResponseIabCall[1].width).to.equal(300); - expect(addBidResponseIabCall[1].height).to.equal(250); - expect(addBidResponseIabCall[1].ad).to.contain(`placementid:'${placementIdIab}',format:'300x250',bidid:'test-bid-id-iab'`, 'ad missing parameters'); - // Verify no attempt to log error - expect(logError.called).to.equal(false, 'logError called'); + + expect(bidResponseNative.cpm).to.equal(1.23); + 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.creativeId).to.equal(placementIdNative); + expect(bidResponseNative.netRevenue).to.equal(true); + expect(bidResponseNative.currency).to.equal('USD'); + expect(bidResponseNative.hb_bidder).to.equal('fan'); + expect(bidResponseNative.fb_bidid).to.equal('test-bid-id-native'); + expect(bidResponseNative.fb_format).to.equal('native'); + expect(bidResponseNative.fb_placementid).to.equal(placementIdNative); + + expect(bidResponseIab.cpm).to.equal(4.56); + 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.creativeId).to.equal(placementIdIab); + expect(bidResponseIab.netRevenue).to.equal(true); + expect(bidResponseIab.currency).to.equal('USD'); + expect(bidResponseIab.hb_bidder).to.equal('fan'); + expect(bidResponseIab.fb_bidid).to.equal('test-bid-id-iab'); + expect(bidResponseIab.fb_format).to.equal('300x250'); + expect(bidResponseIab.fb_placementid).to.equal(placementIdIab); }); it('valid video bid in response', () => { const bidId = 'test-bid-id-video'; - // Valid response - server.respondWith(JSON.stringify({ - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: bidId, - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] + + const [bidResponse] = interpretResponse({ + body: { + errors: [], + bids: { + [placementId]: [{ + placement_id: placementId, + bid_id: bidId, + bid_price_cents: 123, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }] + } } - })); - // Request bids - new AudienceNetwork().callBids({ - bidderCode, - bids: [{ - bidder: bidderCode, - placementCode, - params: { - placementId, - format: 'video' - }, - sizes: [[playerwidth, playerheight]] - }] + }, { + adformats: ['video'], + requestIds: [requestId], + sizes: [[playerwidth, playerheight]] }); - server.respond(); - // Verify addBidResponse call - expect(addBidResponse.calledOnce).to.equal(true); - const addBidResponseArgs = addBidResponse.args[0]; - expect(addBidResponseArgs).to.have.lengthOf(2); - expect(addBidResponseArgs[0]).to.equal(placementCode); - expect(addBidResponseArgs[1].getStatusCode()).to.equal(STATUS.GOOD); - expect(addBidResponseArgs[1].cpm).to.equal(1.23); - expect(addBidResponseArgs[1].bidderCode).to.equal(bidderCode); - // Video-specific properties - expect(addBidResponseArgs[1].mediaType).to.equal('video'); - expect(addBidResponseArgs[1].vastUrl) - .to.equal(addBidResponseArgs[1].descriptionUrl) - .and.to.contain('https://an.facebook.com/v1/instream/vast.xml?') - .and.to.contain(`placementid=${placementId}`) - .and.to.contain('pageurl=http%3A%2F%2F') - .and.to.contain(`playerwidth=${playerwidth}`) - .and.to.contain(`playerheight=${playerheight}`) - .and.to.contain(`bidid=${bidId}`); - expect(addBidResponseArgs[1].width).to.equal(playerwidth); - expect(addBidResponseArgs[1].height).to.equal(playerheight); - // Verify no attempt to log error - expect(logError.called).to.equal(false, 'logError called'); + + expect(bidResponse.cpm).to.equal(1.23); + expect(bidResponse.requestId).to.equal(requestId); + expect(bidResponse.mediaType).to.equal('video'); + expect(bidResponse.vastUrl).to.equal(`https://an.facebook.com/v1/instream/vast.xml?placementid=${placementId}&pageurl=&playerwidth=${playerwidth}&playerheight=${playerheight}&bidid=${bidId}`); + expect(bidResponse.width).to.equal(playerwidth); + expect(bidResponse.height).to.equal(playerheight); }); it('mixed video and native bids', () => { @@ -546,81 +420,83 @@ describe('AudienceNetwork adapter', () => { const videoBidId = 'test-video-bid-id'; const nativePlacementId = 'test-native-placement-id'; const nativeBidId = 'test-native-bid-id'; - // Valid response - server.respondWith(JSON.stringify({ - errors: [], - bids: { - [videoPlacementId]: [{ - placement_id: videoPlacementId, - bid_id: videoBidId, - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }], - [nativePlacementId]: [{ - placement_id: nativePlacementId, - bid_id: nativeBidId, - bid_price_cents: 456, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] + + const [bidResponseVideo, bidResponseNative] = interpretResponse({ + body: { + errors: [], + bids: { + [videoPlacementId]: [{ + placement_id: videoPlacementId, + bid_id: videoBidId, + bid_price_cents: 123, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }], + [nativePlacementId]: [{ + placement_id: nativePlacementId, + bid_id: nativeBidId, + bid_price_cents: 456, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }] + } + } + }, { + adformats: ['video', 'native'], + requestIds: [requestId, requestId], + sizes: [[playerwidth, playerheight], [300, 250]] + }); + + expect(bidResponseVideo.cpm).to.equal(1.23); + expect(bidResponseVideo.requestId).to.equal(requestId); + expect(bidResponseVideo.mediaType).to.equal('video'); + expect(bidResponseVideo.vastUrl).to.equal(`https://an.facebook.com/v1/instream/vast.xml?placementid=${videoPlacementId}&pageurl=&playerwidth=${playerwidth}&playerheight=${playerheight}&bidid=${videoBidId}`); + expect(bidResponseVideo.width).to.equal(playerwidth); + expect(bidResponseVideo.height).to.equal(playerheight); + + expect(bidResponseNative.cpm).to.equal(4.56); + expect(bidResponseNative.requestId).to.equal(requestId); + expect(bidResponseNative.width).to.equal(300); + expect(bidResponseNative.height).to.equal(250); + expect(bidResponseNative.ad).to.contain(`placementid:'${nativePlacementId}',format:'native',bidid:'${nativeBidId}'`); + }); + + it('mixture of valid native bid and error in response', () => { + const [bidResponse] = interpretResponse({ + body: { + errors: ['test-error-message'], + bids: { + [placementId]: [{ + placement_id: placementId, + bid_id: 'test-bid-id', + bid_price_cents: 123, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }] + } } - })); - // Request bids - new AudienceNetwork().callBids({ - bidderCode, - bids: [{ - bidder: bidderCode, - placementCode, - params: { - placementId: videoPlacementId, - format: 'video' - }, - sizes: [[playerwidth, playerheight]] - }, { - bidder: bidderCode, - placementCode, - params: { - placementId: nativePlacementId, - format: 'native' - }, - sizes: [[300, 250]] - }] + }, { + adformats: ['native'], + requestIds: [requestId], + sizes: [[300, 250]] }); - server.respond(); - // Verify multiple attempts to call addBidResponse - expect(addBidResponse.calledTwice).to.equal(true); - // Verify video - const addBidResponseVideoCall = addBidResponse.args[0]; - expect(addBidResponseVideoCall).to.have.lengthOf(2); - expect(addBidResponseVideoCall[0]).to.equal(placementCode); - expect(addBidResponseVideoCall[1].getStatusCode()).to.equal(STATUS.GOOD); - expect(addBidResponseVideoCall[1].cpm).to.equal(1.23); - expect(addBidResponseVideoCall[1].bidderCode).to.equal(bidderCode); - // Video-specific properties - expect(addBidResponseVideoCall[1].mediaType).to.equal('video'); - expect(addBidResponseVideoCall[1].vastUrl) - .to.equal(addBidResponseVideoCall[1].descriptionUrl) - .and.to.contain('https://an.facebook.com/v1/instream/vast.xml?') - .and.to.contain(`placementid=${videoPlacementId}`) - .and.to.contain('pageurl=http%3A%2F%2F') - .and.to.contain(`playerwidth=${playerwidth}`) - .and.to.contain(`playerheight=${playerheight}`) - .and.to.contain(`bidid=${videoBidId}`); - expect(addBidResponseVideoCall[1].width).to.equal(playerwidth); - expect(addBidResponseVideoCall[1].height).to.equal(playerheight); - // Verify native - const addBidResponseNativeCall = addBidResponse.args[1]; - expect(addBidResponseNativeCall).to.have.lengthOf(2); - expect(addBidResponseNativeCall[0]).to.equal(placementCode); - expect(addBidResponseNativeCall[1].getStatusCode()).to.equal(STATUS.GOOD); - expect(addBidResponseNativeCall[1].cpm).to.equal(4.56); - expect(addBidResponseNativeCall[1].bidderCode).to.equal(bidderCode); - expect(addBidResponseNativeCall[1].width).to.equal(300); - expect(addBidResponseNativeCall[1].height).to.equal(250); - expect(addBidResponseNativeCall[1].ad).to.contain(`placementid:'${nativePlacementId}',format:'native',bidid:'${nativeBidId}'`); - // Verify no attempt to log error - expect(logError.called).to.equal(false, 'logError called'); + + expect(bidResponse.cpm).to.equal(1.23); + expect(bidResponse.requestId).to.equal(requestId); + 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') + .and.to.contain('getElementsByTagName("style")', 'ad missing native styles') + .and.to.contain('
', 'ad missing native container'); + expect(bidResponse.creativeId).to.equal(placementId); + expect(bidResponse.netRevenue).to.equal(true); + expect(bidResponse.currency).to.equal('USD'); + + expect(bidResponse.hb_bidder).to.equal('fan'); + expect(bidResponse.fb_bidid).to.equal('test-bid-id'); + expect(bidResponse.fb_format).to.equal('native'); + expect(bidResponse.fb_placementid).to.equal(placementId); }); }); }); diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index 3c9b6d47e9c..65bc0818a6c 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -1,131 +1,444 @@ import { expect } from 'chai'; -import BeachfrontAdapter from 'modules/beachfrontBidAdapter'; -import bidmanager from 'src/bidmanager'; - -const ENDPOINT = '//reachms.bfmio.com/bid.json?exchange_id=11bc5dd5-7421-4dd8-c926-40fa653bec76'; - -const REQUEST = { - 'width': 640, - 'height': 480, - 'bidId': '2a1444be20bb2c', - 'bidder': 'beachfront', - 'bidderRequestId': '7101db09af0db2', - 'params': { - 'appId': 'whatever', - 'video': {}, - 'placementCode': 'video', - 'sizes': [ - 640, 480 - ] - }, - 'bids': [ - { - 'bidFloor': 0.01, - 'bidder': 'beachfront', - 'params': { - 'appId': '11bc5dd5-7421-4dd8-c926-40fa653bec76', - 'bidfloor': 0.01, - 'dev': true - }, - 'placementCode': 'video', - 'sizes': [640, 480], - 'bidId': '2a1444be20bb2c', - 'bidderRequestId': '7101db09af0db2', - 'requestId': '979b659e-ecff-46b8-ae03-7251bae4b725' - } - ], - 'requestId': '979b659e-ecff-46b8-ae03-7251bae4b725', -}; -var RESPONSE = { - 'bidPrice': 5.00, - 'url': 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da:0a47f4ce-d91f-48d0-bd1c-64fa2c196f13:2.90&dsp=58bf26882aba5e6ad608beda,0.612&i_type=pre' -}; +import { spec, VIDEO_ENDPOINT, BANNER_ENDPOINT, OUTSTREAM_SRC, DEFAULT_MIMES } from 'modules/beachfrontBidAdapter'; +import * as utils from 'src/utils'; describe('BeachfrontAdapter', () => { - let adapter; + let bidRequests; - beforeEach(() => adapter = new BeachfrontAdapter()); + beforeEach(() => { + bidRequests = [ + { + bidder: 'beachfront', + params: { + bidfloor: 2.00, + appId: '3b16770b-17af-4d22-daff-9606bdf2c9c3' + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + bidId: '25186806a41eab', + bidderRequestId: '15bdd8d4a0ebaf', + auctionId: 'f17d62d0-e3e3-48d0-9f73-cb4ea358a309' + }, { + bidder: 'beachfront', + params: { + bidfloor: 1.00, + appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' + }, + adUnitCode: 'div-gpt-ad-1460505748561-1', + bidId: '365088ee6d649d', + bidderRequestId: '15bdd8d4a0ebaf', + auctionId: 'f17d62d0-e3e3-48d0-9f73-cb4ea358a309' + } + ]; + }); - describe('request function', () => { - let xhr; - let requests; - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); + describe('spec.isBidRequestValid', () => { + it('should return true when the required params are passed', () => { + const bidRequest = bidRequests[0]; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }); - afterEach(() => xhr.restore()); + it('should return false when the "bidfloor" param is missing', () => { + const bidRequest = bidRequests[0]; + bidRequest.params = { + appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); + it('should return false when the "appId" param is missing', () => { + const bidRequest = bidRequests[0]; + bidRequest.params = { + bidfloor: 5.00 + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); - it('requires parameters to make request', () => { - adapter.callBids({}); - expect(requests).to.be.empty; + it('should return false when no bid params are passed', () => { + const bidRequest = bidRequests[0]; + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); - it('sends bid request to ENDPOINT via POST', () => { - adapter.callBids(REQUEST); - expect(requests[0].url).to.equal(ENDPOINT); - expect(requests[0].method).to.equal('POST'); + it('should return false when a bid request is not passed', () => { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).to.equal(false); }); }); - describe('response handler', () => { - let server; + describe('spec.buildRequests', () => { + describe('for video bids', () => { + it('should attach the bid request object', () => { + bidRequests[0].mediaTypes = { video: {} }; + bidRequests[1].mediaTypes = { video: {} }; + const requests = spec.buildRequests(bidRequests); + expect(requests[0].bidRequest).to.equal(bidRequests[0]); + expect(requests[1].bidRequest).to.equal(bidRequests[1]); + }); - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); - }); + it('should create a POST request for each bid', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(VIDEO_ENDPOINT + bidRequest.params.appId); + }); + + it('should attach request data', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + video: { + playerSize: [ width, height ] + } + }; + const requests = spec.buildRequests([ bidRequest ]); + 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); + expect(data.id).to.be.a('string'); + expect(data.imp[0].video).to.deep.contain({ w: width, h: height, mimes: DEFAULT_MIMES }); + expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); + expect(data.site).to.deep.equal({ page: topLocation.href, domain: topLocation.hostname }); + expect(data.device).to.deep.contain({ ua: navigator.userAgent, language: navigator.language, js: 1 }); + expect(data.cur).to.deep.equal(['USD']); + }); + + it('must parse bid size from a nested array', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + video: { + playerSize: [[ width, height ]] + } + }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.contain({ w: width, h: height }); + }); + + it('must parse bid size from a string', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + video: { + playerSize: `${width}x${height}` + } + }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.contain({ w: width, h: height }); + }); + + it('must handle an empty bid size', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + video: { + playerSize: [] + } + }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.contain({ w: undefined, h: undefined }); + }); + + it('must fall back to the size on the bid object', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.sizes = [ width, height ]; + bidRequest.mediaTypes = { video: {} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.contain({ w: width, h: height }); + }); + + it('must override video targeting params', () => { + const bidRequest = bidRequests[0]; + const mimes = ['video/webm']; + bidRequest.mediaTypes = { video: {} }; + bidRequest.params.video = { mimes }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.contain({ mimes }); + }); - afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); + it('must add GDPR consent data to the request', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + gdprConsent: { + consentRequired: true, + consentString + } + }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.equal(consentString); + }); }); - it('registers bids', () => { - server.respondWith(JSON.stringify(RESPONSE)); + describe('for banner bids', () => { + it('should attach the bid requests array', () => { + bidRequests[0].mediaTypes = { banner: {} }; + bidRequests[1].mediaTypes = { banner: {} }; + const requests = spec.buildRequests(bidRequests); + expect(requests[0].bidRequest).to.deep.equal(bidRequests); + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('should create a single POST request for all bids', () => { + bidRequests[0].mediaTypes = { banner: {} }; + bidRequests[1].mediaTypes = { banner: {} }; + const requests = spec.buildRequests(bidRequests); + expect(requests.length).to.equal(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(BANNER_ENDPOINT); + }); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('cpm', 5.00); + it('should attach request data', () => { + const width = 300; + const height = 250; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + banner: { + sizes: [ width, height ] + } + }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + const topLocation = utils.getTopWindowLocation(); + expect(data.slots).to.deep.equal([ + { + slot: bidRequest.adUnitCode, + id: bidRequest.params.appId, + bidfloor: bidRequest.params.bidfloor, + sizes: [{ w: width, h: height }] + } + ]); + expect(data.page).to.equal(topLocation.href); + expect(data.domain).to.equal(topLocation.hostname); + expect(data.search).to.equal(topLocation.search); + expect(data.ua).to.equal(navigator.userAgent); + }); + + it('must parse bid size from a nested array', () => { + const width = 300; + const height = 250; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + banner: { + sizes: [[ width, height ]] + } + }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.slots[0].sizes).to.deep.equal([ + { w: width, h: height } + ]); + }); + + it('must parse bid size from a string', () => { + const width = 300; + const height = 250; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + banner: { + sizes: `${width}x${height}` + } + }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.slots[0].sizes).to.deep.equal([ + { w: width, h: height } + ]); + }); + + it('must handle an empty bid size', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + banner: { + sizes: [] + } + }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.slots[0].sizes).to.deep.equal([]); + }); + + it('must fall back to the size on the bid object', () => { + const width = 300; + const height = 250; + const bidRequest = bidRequests[0]; + bidRequest.sizes = [ width, height ]; + bidRequest.mediaTypes = { banner: {} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.slots[0].sizes).to.deep.contain({ w: width, h: height }); + }); + + it('must add GDPR consent data to the request', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + gdprConsent: { + consentRequired: true, + consentString + } + }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.gdpr).to.equal(1); + expect(data.gdprConsent).to.equal(consentString); + }); }); + }); + + describe('spec.interpretResponse', () => { + describe('for video bids', () => { + it('should return no bids if the response is not valid', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "url" is missing', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const serverResponse = { + bidPrice: 5.00 + }; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); - it('handles nobid responses', () => { - server.respondWith(JSON.stringify({ - 'bidPrice': 5.00 - })); + it('should return no bids if the response "bidPrice" is missing', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const serverResponse = { + url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da' + }; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('should return a valid video 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', + cmpId: '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, + vastUrl: serverResponse.url, + width: width, + height: height, + renderer: null, + mediaType: 'video', + currency: 'USD', + netRevenue: true, + ttl: 300 + }); + }); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); + it('should return a renderer for outstream video bids', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + video: { + context: 'outstream' + } + }; + 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.renderer).to.deep.contain({ + id: bidRequest.bidId, + url: OUTSTREAM_SRC + }); + }); }); - it('handles JSON.parse errors', () => { - server.respondWith(''); + describe('for banner bids', () => { + it('should return no bids if the response is not valid', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('should return no bids if the response is empty', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: [] }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); + it('should return valid banner bid responses', () => { + bidRequests[0].mediaTypes = { + banner: { + sizes: [[ 300, 250 ], [ 728, 90 ]] + } + }; + bidRequests[1].mediaTypes = { + banner: { + sizes: [[ 300, 600 ], [ 200, 200 ]] + } + }; + const serverResponse = [{ + slot: bidRequests[0].adUnitCode, + adm: '
', + crid: 'crid_1', + price: 3.02, + w: 728, + h: 90 + }, { + slot: bidRequests[1].adUnitCode, + adm: '
', + crid: 'crid_2', + price: 3.06, + w: 300, + h: 600 + }]; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest: bidRequests }); + expect(bidResponse.length).to.equal(2); + for (let i = 0; i < bidRequests.length; i++) { + expect(bidResponse[ i ]).to.deep.equal({ + requestId: bidRequests[ i ].bidId, + bidderCode: spec.code, + ad: serverResponse[ i ].adm, + creativeId: serverResponse[ i ].crid, + cpm: serverResponse[ i ].price, + width: serverResponse[ i ].w, + height: serverResponse[ i ].h, + mediaType: 'banner', + currency: 'USD', + netRevenue: true, + ttl: 300 + }); + } + }); }); }); }); diff --git a/test/spec/modules/bidfluenceBidAdapter_spec.js b/test/spec/modules/bidfluenceBidAdapter_spec.js deleted file mode 100644 index f623954fa9f..00000000000 --- a/test/spec/modules/bidfluenceBidAdapter_spec.js +++ /dev/null @@ -1,71 +0,0 @@ -describe('Bidfluence Adapter', () => { - const expect = require('chai').expect; - const adapter = require('modules/bidfluenceBidAdapter'); - const bidmanager = require('src/bidmanager'); - - var REQUEST = { - bidderCode: 'bidfluence', - sizes: [[300, 250]], - placementCode: 'div-1', - bids: [{ - bidder: 'bidfluence', - params: { - pubId: 'test', - adunitId: 'test' - } - }] - }; - - var RESPONSE = { - ad: 'ad-code', - cpm: 0.9, - width: 300, - height: 250, - placementCode: 'div-1' - }; - - var NO_RESPONSE = { - ad: 'ad-code', - cpm: 0, - width: 300, - height: 250, - placementCode: 'div-1' - }; - - it('Should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.bfPbjsCB).to.exist.and.to.be.a('function'); - }); - - it('Shoud push a valid bid', () => { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - $$PREBID_GLOBAL$$._bidsRequested.push(REQUEST); - adapter(); - $$PREBID_GLOBAL$$.bfPbjsCB(RESPONSE); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('div-1'); - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.bidderCode).to.equal('bidfluence'); - - stubAddBidResponse.restore(); - }); - - it('Shoud push an empty bid', () => { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - $$PREBID_GLOBAL$$._bidsRequested.push(REQUEST); - adapter(); - - $$PREBID_GLOBAL$$.bfPbjsCB(NO_RESPONSE); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('div-1'); - expect(bidObject1.getStatusCode()).to.equal(2); - expect(bidObject1.bidderCode).to.equal('bidfluence'); - - stubAddBidResponse.restore(); - }); -}); diff --git a/test/spec/modules/brainyBidAdapter_spec.js b/test/spec/modules/brainyBidAdapter_spec.js new file mode 100644 index 00000000000..4f619323d7d --- /dev/null +++ b/test/spec/modules/brainyBidAdapter_spec.js @@ -0,0 +1,80 @@ +import { expect } from 'chai'; +import { spec } from 'modules/brainyBidAdapter'; + +const URL = '//proparm.co.jp/ssp/p/pbjs'; +const BIDDER_CODE = 'brainy'; + +const validBidReq = { + bidder: BIDDER_CODE, + params: { + accountID: '12345', + slotID: '12345' + } +}; + +const invalidBidReq = { + bidder: BIDDER_CODE, + params: { + accountID: '', + slotID: '' + } +}; + +const bidReq = [{ + bidder: BIDDER_CODE, + params: { + accountID: '12345', + slotID: '12345' + } +}]; + +const correctReq = { + accountID: '12345', + slotID: '12345' +} + +const bidResponse = { + ad_id: '1036e9746c-d186-49ae-90cb-2796d0f9b223', + adm: '', + cpm: 100, + height: 250, + width: 300 +}; + +describe('brainy Adapter', () => { + describe('request', () => { + it('should validate bid request', () => { + expect(spec.isBidRequestValid(validBidReq)).to.equal(true); + }); + it('should not validate incorrect bid request', () => { + expect(spec.isBidRequestValid(invalidBidReq)).to.equal(false); + }); + }); + describe('build request', () => { + it('Verify bid request', () => { + const request = spec.buildRequests(bidReq); + expect(request[0].method).to.equal('GET'); + expect(request[0].url).to.equal(URL); + expect(request[0].data).to.match(new RegExp(`${correctReq.accountID}`)); + expect(request[0].data).to.match(new RegExp(`${correctReq.slotID}`)); + }); + }); + + describe('interpretResponse', () => { + it('should build bid array', () => { + const request = spec.buildRequests(bidReq); + const result = spec.interpretResponse({body: bidResponse}, request[0]); + expect(result.length).to.equal(1); + }); + + it('should have all relevant fields', () => { + const request = spec.buildRequests(bidReq); + const result = spec.interpretResponse({body: bidResponse}, request[0]); + const bid = result[0]; + + expect(bid.cpm).to.equal(bidResponse.cpm); + expect(bid.width).to.equal(bidResponse.width); + expect(bid.height).to.equal(bidResponse.height); + }); + }); +}); diff --git a/test/spec/modules/bridgewellBidAdapter_spec.js b/test/spec/modules/bridgewellBidAdapter_spec.js new file mode 100644 index 00000000000..5dae3c474ac --- /dev/null +++ b/test/spec/modules/bridgewellBidAdapter_spec.js @@ -0,0 +1,1164 @@ +import { expect } from 'chai'; +import { spec } from 'modules/bridgewellBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; + +describe('bridgewellBidAdapter', function () { + let bidRequests = [ + { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CLJgEAYYvxUiBXBlbm55KgkIrAIQ-gEaATk' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CgUxMjMzOBIBNiIFcGVubnkqCQisAhD6ARoBOQ' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CgUxMjMzOBIBNiIFcGVubnkqCQisAhD6ARoBOQ', + 'cpmWeight': 0.5 + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ', + 'cpmWeight': -0.5 + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ', + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [728, 90], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ', + }, + 'adUnitCode': 'adunit-code-2', + 'mediaTypes': { + 'banner': { + 'sizes': [728, 90] + } + }, + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ', + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [1, 1], + 'mediaTypes': { + 'native': { + 'title': { + 'required': true, + 'len': 15 + }, + 'body': { + 'required': true + }, + 'image': { + 'required': true, + 'sizes': [150, 150] + }, + 'icon': { + 'required': true, + 'sizes': [50, 50] + }, + 'clickUrl': { + 'required': true + }, + 'cta': { + 'required': true + }, + 'sponsoredBy': { + 'required': true + } + } + }, + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ', + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [1, 1], + 'mediaTypes': { + 'native': { + 'title': { + 'required': false, + 'len': 15 + }, + 'body': { + 'required': false + }, + 'image': { + 'required': false, + 'sizes': [150, 150] + }, + 'icon': { + 'required': false, + 'sizes': [50, 50] + }, + 'clickUrl': { + 'required': false + }, + 'cta': { + 'required': false + }, + 'sponsoredBy': { + 'required': false + } + } + }, + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + 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', () => { + let bidWithoutCpmWeight = { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CLJgEAYYvxUiBXBlbm55KgkIrAIQ-gEaATk' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + let bidWithCorrectCpmWeight = { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CLJgEAYYvxUiBXBlbm55KgkIrAIQ-gEaATk', + 'cpmWeight': 0.5 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + let bidWithUncorrectCpmWeight = { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CLJgEAYYvxUiBXBlbm55KgkIrAIQ-gEaATk', + 'cpmWeight': -1.0 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + let bidWithZeroCpmWeight = { + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CLJgEAYYvxUiBXBlbm55KgkIrAIQ-gEaATk', + 'cpmWeight': 0 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bidWithoutCpmWeight)).to.equal(true); + expect(spec.isBidRequestValid(bidWithCorrectCpmWeight)).to.equal(true); + expect(spec.isBidRequestValid(bidWithUncorrectCpmWeight)).to.equal(false); + expect(spec.isBidRequestValid(bidWithZeroCpmWeight)).to.equal(false); + }); + + it('should return false when required params not found', () => { + expect(spec.isBidRequestValid({})).to.equal(false); + }); + + it('should return false when required params are not passed', () => { + let bidWithoutCpmWeight = Object.assign({}, bidWithoutCpmWeight); + let bidWithCorrectCpmWeight = Object.assign({}, bidWithCorrectCpmWeight); + let bidWithUncorrectCpmWeight = Object.assign({}, bidWithUncorrectCpmWeight); + let bidWithZeroCpmWeight = Object.assign({}, bidWithZeroCpmWeight); + + delete bidWithoutCpmWeight.params; + delete bidWithCorrectCpmWeight.params; + delete bidWithUncorrectCpmWeight.params; + delete bidWithZeroCpmWeight.params; + + bidWithoutCpmWeight.params = { + 'ChannelID': 0 + }; + + bidWithCorrectCpmWeight.params = { + 'ChannelID': 0 + }; + + bidWithUncorrectCpmWeight.params = { + 'ChannelID': 0 + }; + + bidWithZeroCpmWeight.params = { + 'ChannelID': 0 + }; + + expect(spec.isBidRequestValid(bidWithoutCpmWeight)).to.equal(false); + expect(spec.isBidRequestValid(bidWithCorrectCpmWeight)).to.equal(false); + expect(spec.isBidRequestValid(bidWithUncorrectCpmWeight)).to.equal(false); + expect(spec.isBidRequestValid(bidWithZeroCpmWeight)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + it('should attach valid params to the tag', () => { + const request = spec.buildRequests(bidRequests); + const payload = request.data; + const adUnits = payload.adUnits; + + expect(payload).to.be.an('object'); + expect(adUnits).to.be.an('array'); + for (let i = 0, max_i = adUnits.length; i < max_i; i++) { + let adUnit = adUnits[i]; + expect(adUnit).to.have.property('ChannelID').that.is.a('string'); + } + }); + + it('should attach validBidRequests to the tag', () => { + const request = spec.buildRequests(bidRequests); + const validBidRequests = request.validBidRequests; + expect(validBidRequests).to.deep.equal(bidRequests); + }); + }); + + describe('interpretResponse', () => { + const request = spec.buildRequests(bidRequests); + const serverResponses = [ + { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }, + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 728, + 'height': 90, + 'mediaType': 'banner', + 'ad': '
test 728x90
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }, + { + 'id': '8f12c646-3b87-4326-a837-c2a76999f168', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }, + { + 'id': '8f12c646-3b87-4326-a837-c2a76999f168', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ad': '
test 300x250
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }, + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 728, + 'height': 90, + 'mediaType': 'banner', + 'ad': '
test 728x90
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }, + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 728, + 'height': 90, + 'mediaType': 'banner', + 'ad': '
test 728x90
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }, + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 728, + 'height': 90, + 'mediaType': 'banner', + 'ad': '
test 728x90
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }, + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }, + { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + } + ]; + + it('should return all required parameters', () => { + const result = spec.interpretResponse({'body': serverResponses}, request); + result.every(res => expect(res.cpm).to.be.a('number')); + result.every(res => expect(res.width).to.be.a('number')); + result.every(res => expect(res.height).to.be.a('number')); + result.every(res => expect(res.ttl).to.be.a('number')); + result.every(res => expect(res.netRevenue).to.be.a('boolean')); + result.every(res => expect(res.currency).to.be.a('string')); + result.every(res => { + if (res.ad) { + expect(res.ad).to.be.an('string'); + } else if (res.native) { + expect(res.native).to.be.an('object'); + } + }); + }); + + it('should give up bid if server response is undefiend', () => { + const result = spec.interpretResponse({'body': undefined}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if request sizes is missing', () => { + let target = Object.assign({}, serverResponses[0]); + target.consumed = false; + const result = spec.interpretResponse({'body': [target]}, spec.buildRequests([{ + 'bidder': 'bridgewell', + 'params': { + 'ChannelID': 'CLJgEAYYvxUiBXBlbm55KgkIrAIQ-gEaATk' + }, + 'adUnitCode': 'adunit-code-1', + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }])); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if response sizes is invalid', () => { + let target = { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'ad': '
test 300x250
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if cpm is missing', () => { + let target = { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'width': 300, + 'height': 250, + 'ad': '
test 300x250
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if width or height is missing', () => { + let target = { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'ad': '
test 300x250
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if ad is missing', () => { + let target = { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if revenue mode is missing', () => { + let target = { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 300, + 'height': 250, + 'ad': '
test 300x250
', + 'ttl': 360, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if currency is missing', () => { + let target = { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 300, + 'height': 250, + 'ad': '
test 300x250
', + 'ttl': 360, + 'netRevenue': true + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if mediaType is missing', () => { + let target = { + 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 300, + 'height': 250, + 'ad': '
test 300x250
', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if property native of mediaType native is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native title is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native title is too long', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-titletest-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native body is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + + it('should give up bid if native image url is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + }); + + it('should give up bid if native image is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native image url is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native image sizes is unmatch', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg' + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native sponsoredBy is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native icon is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native icon url is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native icon sizes is unmatch', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg' + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native clickUrl is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native clickTrackers is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native clickTrackers is empty', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': [], + 'impressionTrackers': ['https://img.scupio.com/test-impressionTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native impressionTrackers is missing', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if native impressionTrackers is empty', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'native', + 'native': { + 'image': { + 'url': 'https://img.scupio.com/test/test-image.jpg', + 'width': 150, + 'height': 150 + }, + 'title': 'test-title', + 'sponsoredBy': 'test-sponsoredBy', + 'body': 'test-body', + 'icon': { + 'url': 'https://img.scupio.com/test/test-icon.jpg', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://img.scupio.com/test-clickUrl', + 'clickTrackers': ['https://img.scupio.com/test-clickTracker'], + 'impressionTrackers': [] + }, + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + + it('should give up bid if mediaType is not support', () => { + let target = { + 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', + 'bidder_code': 'bridgewell', + 'cpm': 5.0, + 'width': 1, + 'height': 1, + 'mediaType': 'superNiceAd', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'NTD' + }; + + const result = spec.interpretResponse({'body': [target]}, request); + expect(result).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/c1xBidAdapter_spec.js b/test/spec/modules/c1xBidAdapter_spec.js index 3e482dfaae9..0517668ea0d 100644 --- a/test/spec/modules/c1xBidAdapter_spec.js +++ b/test/spec/modules/c1xBidAdapter_spec.js @@ -1,186 +1,164 @@ -import {expect} from 'chai'; -import C1XAdapter from 'modules/c1xBidAdapter'; -import bidmanager from 'src/bidmanager'; -import adLoader from 'src/adloader'; - -let getDefaultBidRequest = () => { - return { - bidderCode: 'c1x', - bids: [{ - bidder: 'c1x', - sizes: [[300, 250], [300, 600]], - params: { - siteId: '999', - pixelId: '9999', - placementCode: 'div-c1x-ht', - domain: 'http://c1exchange.com/' - } - }] - }; -}; - -let getDefaultBidResponse = () => { - return { - bid: true, - adId: 'div-c1x-ht', - cpm: 3.31, - ad: '
', - width: 300, - height: 250 - }; -}; - -describe('c1x adapter tests: ', () => { - let pbjs = window.$$PREBID_GLOBAL$$ || {}; - let stubLoadScript; - let adapter; - - function createBidderRequest(bids) { - let bidderRequest = getDefaultBidRequest(); - if (bids && Array.isArray(bids)) { - bidderRequest.bids = bids; - } - return bidderRequest; - } - - beforeEach(() => { - adapter = new C1XAdapter(); - }); +import { expect } from 'chai'; +import { c1xAdapter } from 'modules/c1xBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const ENDPOINT = 'https://ht.c1exchange.com/ht'; +const BIDDER_CODE = 'c1x'; + +describe('C1XAdapter', () => { + const adapter = newBidder(c1xAdapter); - describe('check callBids()', () => { + describe('inherited functions', () => { it('exists and is a function', () => { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); - describe('creation of bid url', () => { - beforeEach(() => { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - afterEach(() => { - stubLoadScript.restore(); - }); - it('should be called only once', () => { - adapter.callBids(getDefaultBidRequest()); - sinon.assert.calledOnce(stubLoadScript); - expect(window._c1xResponse).to.exist.and.to.be.a('function'); - }); - it('require parameters before call', () => { - let xhr; - let requests; - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - adapter.callBids(getDefaultBidRequest()); - expect(requests).to.be.empty; - xhr.restore(); - }); - it('should send with correct parameters', () => { - adapter.callBids(getDefaultBidRequest()); - let expectedUrl = stubLoadScript.getCall(0).args[0]; - sinon.assert.calledWith(stubLoadScript, expectedUrl); - }); - it('should hit endpoint with optional param', () => { - let bids = [{ - bidder: 'c1x', - sizes: [[300, 250], [300, 600]], - params: { - siteId: '999', - placementCode: 'div-c1x-ht', - endpoint: 'http://ht-integration.c1exchange.com:9000/ht', - floorPriceMap: { - '300x250': 4.00 - }, - dspid: '4288' - } - }]; - adapter.callBids(createBidderRequest(bids)); - let expectedUrl = stubLoadScript.getCall(0).args[0]; - sinon.assert.calledWith(stubLoadScript, expectedUrl); - bids[0].sizes = [[728, 90]]; - adapter.callBids(createBidderRequest(bids)); - sinon.assert.calledTwice(stubLoadScript); - }); - it('should hit default bidder endpoint', () => { - let bid = getDefaultBidRequest(); - bid.bids[0].params.endpoint = null; - adapter.callBids(bid); - let expectedUrl = stubLoadScript.getCall(0).args[0]; - sinon.assert.calledWith(stubLoadScript, expectedUrl); - }); - it('should throw error msg if no site id provided', () => { - let bid = getDefaultBidRequest(); - bid.bids[0].params.siteId = ''; - adapter.callBids(bid); - sinon.assert.notCalled(stubLoadScript); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': BIDDER_CODE, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'params': { + 'siteId': '9999' + } + }; + + it('should return true when required params are passed', () => { + expect(c1xAdapter.isBidRequestValid(bid)).to.equal(true); }); - it('should not inject audience pixel if no pixelId provided', () => { - let bid = getDefaultBidRequest(); - let responsePId; - bid.bids[0].params.pixelId = ''; - adapter.callBids(bid); + + it('should return false when required params are not found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'siteId': null + }; + expect(c1xAdapter.isBidRequestValid(bid)).to.equal(false); }); }); - describe('bid response', () => { - let server; - let stubAddBidResponse; - beforeEach(() => { - adapter = new C1XAdapter(); - server = sinon.fakeServer.create(); - stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - }); - afterEach(() => { - server.restore(); - stubAddBidResponse.restore(); + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'siteId': '9999' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + const parseRequest = (data) => { + const parsedData = '{"' + data.replace(/=|&/g, (foundChar) => { + if (foundChar == '=') return '":"'; + else if (foundChar == '&') return '","'; + }) + '"}' + return parsedData; + }; + + it('sends bid request to ENDPOINT via GET', () => { + const request = c1xAdapter.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); }); - it('callback function should exist', function () { - expect(pbjs._c1xResponse).to.exist.and.to.be.a('function'); + it('should generate correct bid Id tag', () => { + const request = c1xAdapter.buildRequests(bidRequests); + expect(request.bids[0].adUnitCode).to.equal('adunit-code'); + expect(request.bids[0].bidId).to.equal('30b31c1838de1e'); }); - it('should get JSONP from c1x bidder', function () { - let responses = []; - let stubC1XResponseFunc = sinon.stub(pbjs, '_c1xResponse'); - responses.push(getDefaultBidResponse()); - window._c1xResponse(JSON.stringify(responses)); - sinon.assert.calledOnce(stubC1XResponseFunc); - stubC1XResponseFunc.restore(); + + it('should convert params to proper form and attach to request', () => { + const request = c1xAdapter.buildRequests(bidRequests); + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.adunits).to.equal('1'); + expect(payloadObj.a1s).to.equal('300x250,300x600'); + expect(payloadObj.a1).to.equal('adunit-code'); + expect(payloadObj.site).to.equal('9999'); }); - it('should be added to bidmanager after returned from bidder', () => { - let responses = []; - responses.push(getDefaultBidResponse()); - pbjs._c1xResponse(responses); - sinon.assert.calledOnce(stubAddBidResponse); + + it('should convert floor price to proper form and attach to request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + 'params': { + 'siteId': '9999', + 'floorPriceMap': { + '300x250': 4.35 + } + } + }); + const request = c1xAdapter.buildRequests([bidRequest]); + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.a1p).to.equal('4.35'); }); - it('should send correct arguments to bidmanager.addBidResponse', () => { - let responses = []; - responses.push(getDefaultBidResponse()); - pbjs._c1xResponse(JSON.stringify(responses)); - var responseAdId = stubAddBidResponse.getCall(0).args[0]; - var bidObject = stubAddBidResponse.getCall(0).args[1]; - expect(responseAdId).to.equal('div-c1x-ht'); - expect(bidObject.cpm).to.equal(3.31); - expect(bidObject.width).to.equal(300); - expect(bidObject.height).to.equal(250); - expect(bidObject.ad).to.equal('
'); - expect(bidObject.bidderCode).to.equal('c1x'); - sinon.assert.calledOnce(stubAddBidResponse); + + it('should convert pageurl to proper form and attach to request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + 'params': { + 'siteId': '9999', + 'pageurl': 'http://c1exchange.com/' + } + }); + const request = c1xAdapter.buildRequests([bidRequest]); + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.pageurl).to.equal('http://c1exchange.com/'); }); - it('should response to bidmanager when it is a no bid', () => { - let responses = []; - responses.push({'bid': false, 'adId': 'div-gpt-ad-1494499685685-0'}); - pbjs._c1xResponse(responses); - let responseAdId = stubAddBidResponse.getCall(0).args[0]; - let bidObject = stubAddBidResponse.getCall(0).args[1]; - expect(responseAdId).to.equal('div-gpt-ad-1494499685685-0'); - expect(bidObject.statusMessage).to.equal('Bid returned empty or error response'); - sinon.assert.calledOnce(stubAddBidResponse); + }); + + describe('interpretResponse', () => { + let response = { + 'bid': true, + 'cpm': 1.5, + 'ad': '', + 'width': 300, + 'height': 250, + 'crid': '8888', + 'adId': 'c1x-test', + 'bidType': 'GROSS_BID' + }; + + it('should get correct bid response', () => { + let expectedResponse = [ + { + width: 300, + height: 250, + cpm: 1.5, + ad: '', + creativeId: '8888', + currency: 'USD', + ttl: 300, + netRevenue: false, + requestId: 'yyyy' + } + ]; + let bidderRequest = {}; + bidderRequest.bids = [ + { adUnitCode: 'c1x-test', + bidId: 'yyyy' } + ]; + let result = c1xAdapter.interpretResponse({ body: [response] }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('should show error when bidder sends invalid bid responses', () => { - let responses; - pbjs._c1xResponse(responses); - let bidObject = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject.statusMessage).to.equal('Bid returned empty or error response'); - sinon.assert.calledOnce(stubAddBidResponse); + + it('handles nobid responses', () => { + let response = { + bid: false, + adId: 'c1x-test' + }; + let bidderRequest = {}; + let result = c1xAdapter.interpretResponse({ body: [response] }, bidderRequest); + expect(result.length).to.equal(0); }); }); }); diff --git a/test/spec/modules/carambolaBidAdapter_spec.js b/test/spec/modules/carambolaBidAdapter_spec.js deleted file mode 100644 index 7611c9d9370..00000000000 --- a/test/spec/modules/carambolaBidAdapter_spec.js +++ /dev/null @@ -1,139 +0,0 @@ -import {expect} from 'chai'; -import * as utils from 'src/utils'; -import CarambolaAdapter from 'modules/carambolaBidAdapter'; -import bidmanager from 'src/bidmanager'; - -const DEFAULT_BIDDER_REQUEST = { - bidderCode: 'carambola', - requestId: 'c9ad932a-41d9-4821-b6dc-0c8146029faf', - adId: '2e3daacdeed03d', - start: new Date().getTime(), - bids: [{ - bidder: 'carambola', - adId: '2e3daacdeed03d', - requestId: 'c9ad932a-41d9-4821-b6dc-0c8146029faf', - adUnitCode: 'cbola_prebid_code_97', - token: 'CGYCLyIy', - pageViewId: '22478638', - params: { - pid: 'hbtest', - did: 112591, - wid: 0 - } - }] -}; - -const DEFAULT_HB_RESPONSE = { - cpm: 0.1693953107111156, - ad: ' ', - token: '9cd6bf9c-433d-4663-b67f-da727f4cebff', - width: '300', - height: '250', - currencyCode: 'USD', - pageViewId: '22478638', - requestStatus: 1 - -}; - -describe('carambolaAdapter', function () { - let adapter; - - beforeEach(() => adapter = new CarambolaAdapter()); - - function createBidderRequest({bids, params} = {}) { - var bidderRequest = utils.cloneJson(DEFAULT_BIDDER_REQUEST); - if (bids && Array.isArray(bids)) { - bidderRequest.bids = bids; - } - if (params) { - bidderRequest.bids.forEach(bid => bid.params = params); - } - return bidderRequest; - } - - describe('callBids()', () => { - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - - // bid request starts - describe('bid request', () => { - let xhr; - let requests; - - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - }); - - afterEach(() => xhr.restore()); - - it('requires parameters to be made', () => { - adapter.callBids({}); - expect(requests[0]).to.be.empty; - }); - - it('should hit the default hb.carambo.la endpoint', () => { - adapter.callBids(DEFAULT_BIDDER_REQUEST); - expect(requests[0].url).to.contain('hb.carambo.la'); - }); - - it('should verifiy that a page_view_id is sent', () => { - adapter.callBids(DEFAULT_BIDDER_REQUEST); - expect(requests[0].url).to.contain('pageViewId='); - }); - - it('should should send the correct did', () => { - adapter.callBids(createBidderRequest({ - params: { - did: 112591, - wid: 0 - } - })); - expect(requests[0].url).to.contain('did=112591'); - }); - }); - // bid request ends - - // bid response starts - describe('bid response', () => { - let server; - - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); - }); - - afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); - }); - - it('should be added to bidmanager if response is valid', () => { - server.respondWith(JSON.stringify(DEFAULT_HB_RESPONSE)); - adapter.callBids(DEFAULT_BIDDER_REQUEST); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - }); - - it('should be added to bidmanager with correct bidderCode', () => { - server.respondWith(JSON.stringify(DEFAULT_HB_RESPONSE)); - adapter.callBids(DEFAULT_BIDDER_REQUEST); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(bidmanager.addBidResponse.firstCall.args[1]).to.have.property('bidderCode', 'carambola'); - }); - - it('should have pageViewId matching the pageViewId from related bid request', () => { - server.respondWith(JSON.stringify(DEFAULT_HB_RESPONSE)); - adapter.callBids(DEFAULT_BIDDER_REQUEST); - server.respond(); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(bidmanager.addBidResponse.firstCall.args[1]) - .to.have.property('pvid', DEFAULT_BIDDER_REQUEST.bids[0].pageViewId); - }); - }); - // bid response ends - }); -}); diff --git a/test/spec/modules/centroBidAdapter_spec.js b/test/spec/modules/centroBidAdapter_spec.js deleted file mode 100644 index 9f354e1ba56..00000000000 --- a/test/spec/modules/centroBidAdapter_spec.js +++ /dev/null @@ -1,218 +0,0 @@ -describe('centro adapter tests', function () { - var expect = require('chai').expect; - var assert = require('chai').assert; - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - - var adapter = require('modules/centroBidAdapter'); - var bidmanager = require('src/bidmanager'); - var adLoader = require('src/adloader'); - var utils = require('src/utils'); - - let stubLoadScript; - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - - afterEach(function () { - stubLoadScript.restore(); - }); - - var logErrorSpy; - beforeEach(function () { - logErrorSpy = sinon.spy(utils, 'logError'); - }); - - afterEach(function () { - logErrorSpy.restore(); - }); - - describe('creation of bid url', function () { - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; - } - - it('should fix parameter name', function () { - var params = { - bidderCode: 'centro', - bids: [ - { - bidder: 'centro', - sizes: [[300, 250]], - params: { - unit: 28136, - page_url: 'http://test_url.ru' - }, - placementCode: 'div-gpt-ad-12345-1' - }, - { - bidder: 'centro', - sizes: [[728, 90]], - params: { - unit: 28137 - }, - placementCode: 'div-gpt-ad-12345-2' - }, - { - bidder: 'centro', - sizes: [[728, 90]], - params: {}, - placementCode: 'div-gpt-ad-12345-3' - } - ] - }; - - adapter().callBids(params); - var bidUrl1 = stubLoadScript.getCall(0).args[0]; - var bidUrl2 = stubLoadScript.getCall(1).args[0]; - - sinon.assert.calledWith(logErrorSpy, 'Bid has no unit', 'centro'); - sinon.assert.calledWith(stubLoadScript, bidUrl1); - - var parsedBidUrl = urlParse(bidUrl1); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - var generatedCallback = 'window["adCentroHandler_28136300x250div-gpt-ad-12345-1"]'; - - expect(parsedBidUrl.hostname).to.equal('staging.brand-server.com'); - expect(parsedBidUrl.pathname).to.equal('/hb'); - - expect(parsedBidUrlQueryString).to.have.property('s').and.to.equal('28136'); - expect(parsedBidUrlQueryString).to.have.property('url').and.to.equal('http://test_url.ru'); - expect(parsedBidUrlQueryString).to.have.property('sz').and.to.equal('300x250'); - expect(parsedBidUrlQueryString).to.have.property('callback').and.to.equal(generatedCallback); - - sinon.assert.calledWith(stubLoadScript, bidUrl2); - - parsedBidUrl = urlParse(bidUrl2); - parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - generatedCallback = 'window["adCentroHandler_28137728x90div-gpt-ad-12345-2"]'; - - expect(parsedBidUrl.hostname).to.equal('t.brand-server.com'); - expect(parsedBidUrl.pathname).to.equal('/hb'); - - expect(parsedBidUrlQueryString).to.have.property('s').and.to.equal('28137'); - expect(parsedBidUrlQueryString).to.have.property('url').and.to.equal(location.href); - expect(parsedBidUrlQueryString).to.have.property('sz').and.to.equal('728x90'); - expect(parsedBidUrlQueryString).to.have.property('callback').and.to.equal(generatedCallback); - }); - }); - - describe('handling of the callback response', function () { - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; - } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; - } - if (typeof ($$PREBID_GLOBAL$$._adsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._adsReceived = []; - } - - var params = { - bidderCode: 'centro', - bids: [ - { - bidder: 'centro', - sizes: [[300, 250]], - params: { - unit: 28136 - }, - placementCode: '/19968336/header-bid-tag-0' - }, - { - bidder: 'centro', - sizes: [[728, 90]], - params: { - unit: 111111 - }, - placementCode: '/19968336/header-bid-tag-1' - }, - { - bidder: 'centro', - sizes: [[728, 90]], - params: { - unit: 222222 - }, - placementCode: '/19968336/header-bid-tag-2' - }, - { - bidder: 'centro', - sizes: [[728, 90]], - params: { - unit: 333333 - }, - placementCode: '/19968336/header-bid-tag-3' - } - ] - }; - - it('callback function should exist', function () { - adapter().callBids(params); - - expect(window['adCentroHandler_28136300x250%2F19968336%2Fheader-bid-tag-0']) - .to.exist.and.to.be.a('function'); - expect(window['adCentroHandler_111111728x90%2F19968336%2Fheader-bid-tag-1']) - .to.exist.and.to.be.a('function'); - }); - - it('bidmanager.addBidResponse should be called with correct arguments', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - adapter().callBids(params); - - var adUnits = new Array(); - var unit = new Object(); - unit.bids = params.bids; - unit.code = '/19968336/header-bid-tag'; - unit.sizes = [[300, 250], [728, 90]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - var response = {'adTag': '
test content
', 'statusMessage': 'Bid available', 'height': 250, '_comment': '', 'value': 0.2, 'width': 300, 'sectionID': 28136}; - var response2 = {'adTag': '', 'statusMessage': 'No bid', 'height': 0, 'value': 0, 'width': 0, 'sectionID': 111111}; - var response3 = {'adTag': '', 'height': 0, 'value': 0, 'width': 0, 'sectionID': 222222}; - var response4 = ''; - - window['adCentroHandler_28136300x250%2F19968336%2Fheader-bid-tag-0'](response); - window['adCentroHandler_111111728x90%2F19968336%2Fheader-bid-tag-1'](response2); - window['adCentroHandler_222222728x90%2F19968336%2Fheader-bid-tag-2'](response3); - window['adCentroHandler_333333728x90%2F19968336%2Fheader-bid-tag-3'](response4); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - var bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0]; - var bidObject2 = stubAddBidResponse.getCall(1).args[1]; - var bidPlacementCode3 = stubAddBidResponse.getCall(2).args[0]; - var bidObject3 = stubAddBidResponse.getCall(2).args[1]; - var bidPlacementCode4 = stubAddBidResponse.getCall(3).args[0]; - var bidObject4 = stubAddBidResponse.getCall(3).args[1]; - - expect(logErrorSpy.getCall(0).args[0]).to.equal('Requested unit is 222222. Bid has missmatch format.'); - expect(logErrorSpy.getCall(1).args[0]).to.equal('Requested unit is 333333. Response has no bid.'); - - expect(bidPlacementCode1).to.equal('/19968336/header-bid-tag-0'); - expect(bidObject1.cpm).to.equal(0.2); - expect(bidObject1.ad).to.equal('
test content
'); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.height).to.equal(250); - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.bidderCode).to.equal('centro'); - - expect(bidPlacementCode2).to.equal('/19968336/header-bid-tag-1'); - expect(bidObject2.getStatusCode()).to.equal(2); - expect(bidPlacementCode3).to.equal('/19968336/header-bid-tag-2'); - expect(bidObject3.getStatusCode()).to.equal(2); - expect(bidPlacementCode4).to.equal('/19968336/header-bid-tag-3'); - expect(bidObject4.getStatusCode()).to.equal(2); - - stubAddBidResponse.restore(); - }); - }); -}); diff --git a/test/spec/modules/clickforceBidAdapter_spec.js b/test/spec/modules/clickforceBidAdapter_spec.js new file mode 100644 index 00000000000..8b0955590a0 --- /dev/null +++ b/test/spec/modules/clickforceBidAdapter_spec.js @@ -0,0 +1,127 @@ +import { expect } from 'chai'; +import { spec } from 'modules/clickforceBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('ClickforceAdapter', () => { + 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', () => { + let bid = { + 'bidder': 'clickforce', + 'params': { + 'zone': '6682' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }; + + 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', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'someIncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [{ + 'bidder': 'clickforce', + 'params': { + 'zone': '6682' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + + const request = spec.buildRequests(bidRequests); + + it('sends bid request to our endpoint via POST', () => { + expect(request.method).to.equal('POST'); + }); + }); + + describe('interpretResponse', () => { + let response = [{ + 'cpm': 0.5, + 'width': '300', + 'height': '250', + 'callback_uid': '220ed41385952a', + 'type': 'Default Ad', + 'tag': '', + 'creativeId': '1f99ac5c3ef10a4097499a5686b30aff-6682', + 'requestId': '220ed41385952a', + 'currency': 'USD', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682' + }]; + + it('should get the correct bid response', () => { + let expectedResponse = [{ + 'requestId': '220ed41385952a', + 'cpm': 0.5, + 'width': '300', + 'height': '250', + 'creativeId': '1f99ac5c3ef10a4097499a5686b30aff-6682', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 60, + 'ad': '' + }]; + + let bidderRequest; + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles empty bid response', () => { + let response = { + body: {} + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs function', () => { + it('should register type is iframe', () => { + const syncOptions = { + 'iframeEnabled': 'true' + } + let userSync = spec.getUserSyncs(syncOptions); + expect(userSync[0].type).to.equal('iframe'); + expect(userSync[0].url).to.equal('https://cdn.doublemax.net/js/capmapping.htm'); + }); + + it('should register type is image', () => { + const syncOptions = { + 'pixelEnabled': 'true' + } + let userSync = spec.getUserSyncs(syncOptions); + expect(userSync[0].type).to.equal('image'); + expect(userSync[0].url).to.equal('https://c.doublemax.net/cm'); + }); + }); +}); diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index e8435d90679..54952fbf4b5 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -1,127 +1,118 @@ -import { expect } from 'chai'; -import Adapter from '../../../modules/colossussspBidAdapter'; -import adapterManager from 'src/adaptermanager'; -import bidManager from 'src/bidmanager'; -import CONSTANTS from 'src/constants.json'; - -describe('ColossusSSP adapter tests', function () { - let sandbox; - const adUnit = { - code: 'colossusssp', - sizes: [[300, 250], [300, 600]], - bids: [{ - bidder: 'colossusssp', - params: { - placement_id: 0 - } - }] - }; - - const response = { - ad_id: 15, - adm: '
Bid Response
', - cpm: 0.712, - deal: '5e1f0a8f2aa1', - width: 300, - height: 250 +import {expect} from 'chai'; +import {spec} from '../../../modules/colossussspBidAdapter'; + +describe('ColossussspAdapter', () => { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'colossusssp', + bidderRequestId: '145e1d6a7837c9', + params: { + placement_id: 0 + }, + placementCode: 'placementid_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + sizes: [[300, 250]], + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' }; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); + describe('isBidRequestValid', () => { + it('Should return true when placement_id can be cast to a number', () => { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when placement_id is not a number', () => { + bid.params.placement_id = 'aaa'; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); }); - describe('ColossusSSP callBids validation', () => { - let bids, - server; - - beforeEach(() => { - bids = []; - server = sinon.fakeServer.create(); - sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { - bids.push(bid); - }); + describe('buildRequests', () => { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', () => { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; }); - - afterEach(() => { - server.restore(); + it('Returns POST method', () => { + expect(serverRequest.method).to.equal('POST'); }); - - let adapter = adapterManager.bidderRegistry['colossusssp']; - - it('Valid bid-request', () => { - sandbox.stub(adapter, 'callBids'); - adapterManager.callBids({ - adUnits: [clone(adUnit)] - }); - - let bidderRequest = adapter.callBids.getCall(0).args[0]; - - expect(bidderRequest).to.have.property('bids') - .that.is.an('array') - .with.lengthOf(1); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .to.have.property('bidder', 'colossusssp'); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .with.property('sizes') - .that.is.an('array') - .with.lengthOf(2) - .that.deep.equals(adUnit.sizes); - expect(bidderRequest).to.have.deep.property('bids[0]') - .with.property('params') - .to.have.property('placement_id', 0); + it('Returns valid URL', () => { + expect(serverRequest.url).to.equal('//colossusssp.com/?c=o&m=multi'); }); - - it('Valid bid-response', () => { - server.respondWith(JSON.stringify( - response - )); - adapterManager.callBids({ - adUnits: [clone(adUnit)] - }); - server.respond(); - - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bids[0].bidderCode).to.equal('colossusssp'); - expect(bids[0].width).to.equal(300); - expect(bids[0].height).to.equal(250); - expect(bids[0].cpm).to.equal(0.712); - expect(bids[0].dealId).to.equal('5e1f0a8f2aa1'); + it('Returns valid data if array of bids is valid', () => { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + 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', () => { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; }); }); - - describe('MAS mapping / ordering', () => { - let masSizeOrdering = Adapter.masSizeOrdering; - - it('should not include values without a proper mapping', () => { - let ordering = masSizeOrdering([[320, 50], [42, 42], [300, 250], [640, 480], [0, 0]]); - expect(ordering).to.deep.equal([15, 43, 65]); + describe('interpretResponse', () => { + 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', () => { + 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', () => { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); }); + }); - it('should sort values without any MAS priority sizes in regular ascending order', () => { - let ordering = masSizeOrdering([[320, 50], [640, 480], [200, 600]]); - expect(ordering).to.deep.equal([43, 65, 119]); + describe('getUserSyncs', () => { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', () => { + 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('//colossusssp.com/?c=o&m=cookie'); }); - - it('should sort MAS priority sizes in the proper order w/ rest ascending', () => { - let ordering = masSizeOrdering([[320, 50], [640, 480], [300, 250], [200, 600]]); - expect(ordering).to.deep.equal([15, 43, 65, 119]); - - ordering = masSizeOrdering([[320, 50], [300, 250], [640, 480], [200, 600], [728, 90]]); - expect(ordering).to.deep.equal([15, 2, 43, 65, 119]); - - ordering = masSizeOrdering([ [320, 50], [640, 480], [200, 600], [728, 90]]); - expect(ordering).to.deep.equal([2, 43, 65, 119]); - }) }); }); - -function clone(obj) { - return JSON.parse(JSON.stringify(obj)); -} diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js new file mode 100644 index 00000000000..5974ac79324 --- /dev/null +++ b/test/spec/modules/consentManagement_spec.js @@ -0,0 +1,292 @@ +import {setConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, allowAuction} from 'modules/consentManagement'; +import {gdprDataHandler} from 'src/adaptermanager'; +import * as utils from 'src/utils'; +import { config } from 'src/config'; + +let assert = require('chai').assert; +let expect = require('chai').expect; + +describe('consentManagement', function () { + describe('setConfig tests:', () => { + describe('empty setConfig value', () => { + beforeEach(() => { + sinon.stub(utils, 'logInfo'); + }); + + afterEach(() => { + utils.logInfo.restore(); + config.resetConfig(); + }); + + it('should use system default values', () => { + setConfig({}); + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(10000); + expect(allowAuction).to.be.true; + sinon.assert.callCount(utils.logInfo, 3); + }); + }); + + describe('valid setConfig value', () => { + afterEach(() => { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + }); + it('results in all user settings overriding system defaults', () => { + let allConfig = { + cmpApi: 'iab', + timeout: 7500, + allowAuctionWithoutConsent: false + }; + + setConfig(allConfig); + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(7500); + expect(allowAuction).to.be.false; + }); + }); + }); + + describe('requestBidsHook tests:', () => { + let goodConfigWithCancelAuction = { + cmpApi: 'iab', + timeout: 7500, + allowAuctionWithoutConsent: false + }; + + let goodConfigWithAllowAuction = { + cmpApi: 'iab', + timeout: 7500, + allowAuctionWithoutConsent: true + }; + + let didHookReturn; + + afterEach(() => { + gdprDataHandler.consentData = null; + resetConsentData(); + }); + + describe('error checks:', () => { + describe('unknown CMP framework ID:', () => { + beforeEach(() => { + sinon.stub(utils, 'logWarn'); + }); + + afterEach(() => { + utils.logWarn.restore(); + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + gdprDataHandler.consentData = null; + }); + + it('should return Warning message and return to hooked function', () => { + let badCMPConfig = { + cmpApi: 'bad' + }; + setConfig(badCMPConfig); + expect(userCMP).to.be.equal(badCMPConfig.cmpApi); + + didHookReturn = false; + + requestBidsHook({}, () => { + didHookReturn = true; + }); + let consent = gdprDataHandler.getConsentData(); + sinon.assert.calledOnce(utils.logWarn); + expect(didHookReturn).to.be.true; + expect(consent).to.be.null; + }); + }); + }); + + describe('already known consentData:', () => { + let cmpStub = sinon.stub(); + + beforeEach(() => { + didHookReturn = false; + window.__cmp = function() {}; + }); + + afterEach(() => { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + cmpStub.restore(); + delete window.__cmp; + gdprDataHandler.consentData = null; + }); + + it('should bypass CMP and simply use previously stored consentData', () => { + let testConsentData = { + gdprApplies: true, + metadata: 'xyz' + }; + + cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { + args[2](testConsentData); + }); + setConfig(goodConfigWithAllowAuction); + requestBidsHook({}, () => {}); + cmpStub.restore(); + + // reset the stub to ensure it wasn't called during the second round of calls + cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { + args[2](testConsentData); + }); + + requestBidsHook({}, () => { + didHookReturn = true; + }); + let consent = gdprDataHandler.getConsentData(); + + expect(didHookReturn).to.be.true; + expect(consent.consentString).to.equal(testConsentData.metadata); + expect(consent.gdprApplies).to.be.true; + sinon.assert.notCalled(cmpStub); + }); + }); + + describe('CMP workflow for iframed page', () => { + let eventStub = sinon.stub(); + let cmpStub = sinon.stub(); + + beforeEach(() => { + didHookReturn = false; + resetConsentData(); + window.__cmp = function() {}; + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(() => { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + eventStub.restore(); + cmpStub.restore(); + delete window.__cmp; + utils.logError.restore(); + utils.logWarn.restore(); + gdprDataHandler.consentData = null; + }); + + it('should return the consent string from a postmessage + addEventListener response', () => { + let testConsentData = { + data: { + __cmpReturn: { + returnValue: { + gdprApplies: true, + metadata: 'BOJy+UqOJy+UqABAB+AAAAAZ+A==' + } + } + } + }; + eventStub = sinon.stub(window, 'addEventListener').callsFake((...args) => { + args[1](testConsentData); + }); + cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { + args[2]({ + gdprApplies: true, + metadata: 'BOJy+UqOJy+UqABAB+AAAAAZ+A==' + }); + }); + + setConfig(goodConfigWithAllowAuction); + + requestBidsHook({}, () => { + didHookReturn = true; + }); + let consent = gdprDataHandler.getConsentData(); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.consentString).to.equal('BOJy+UqOJy+UqABAB+AAAAAZ+A=='); + expect(consent.gdprApplies).to.be.true; + }); + }); + + describe('CMP workflow for normal pages:', () => { + let cmpStub = sinon.stub(); + + beforeEach(() => { + didHookReturn = false; + resetConsentData(); + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + window.__cmp = function() {}; + }); + + afterEach(() => { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + cmpStub.restore(); + utils.logError.restore(); + utils.logWarn.restore(); + delete window.__cmp; + gdprDataHandler.consentData = null; + }); + + it('performs lookup check and stores consentData for a valid existing user', () => { + let testConsentData = { + gdprApplies: true, + metadata: 'BOJy+UqOJy+UqABAB+AAAAAZ+A==' + }; + cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { + args[2](testConsentData); + }); + + setConfig(goodConfigWithAllowAuction); + + requestBidsHook({}, () => { + didHookReturn = true; + }); + let consent = gdprDataHandler.getConsentData(); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.consentString).to.equal(testConsentData.metadata); + expect(consent.gdprApplies).to.be.true; + }); + + it('throws an error when processCmpData check failed while config had allowAuction set to false', () => { + let testConsentData = null; + + cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { + args[2](testConsentData); + }); + + setConfig(goodConfigWithCancelAuction); + + requestBidsHook({}, () => { + didHookReturn = true; + }); + let consent = gdprDataHandler.getConsentData(); + + sinon.assert.calledOnce(utils.logError); + expect(didHookReturn).to.be.false; + expect(consent).to.be.null; + }); + + it('throws a warning + stores consentData + calls callback when processCmpData check failed while config had allowAuction set to true', () => { + let testConsentData = null; + + cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { + args[2](testConsentData); + }); + + setConfig(goodConfigWithAllowAuction); + + requestBidsHook({}, () => { + didHookReturn = true; + }); + let consent = gdprDataHandler.getConsentData(); + + sinon.assert.calledOnce(utils.logWarn); + expect(didHookReturn).to.be.true; + expect(consent.consentString).to.be.undefined; + expect(consent.gdprApplies).to.be.undefined; + }); + }); + }); +}); diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js new file mode 100644 index 00000000000..b87ce6634f6 --- /dev/null +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -0,0 +1,215 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils'; +import {spec} from 'modules/consumableBidAdapter'; +import {config} from 'src/config'; + +const DEFAULT_OAD_CONTENT = ''; +const DEFAULT_AD_CONTENT = '' + +let getDefaultBidResponse = () => { + return { + id: '245730051428950632', + cur: 'USD', + seatbid: [{ + bid: [{ + id: 1, + impid: '245730051428950632', + price: 0.09, + adm: DEFAULT_OAD_CONTENT, + crid: 'creative-id', + h: 90, + w: 728, + dealid: 'deal-id', + ext: {sizeid: 225} + }] + }] + }; +}; + +let getBidParams = () => { + return { + placement: 1234567, + network: '9599.1', + unitId: '987654', + unitName: 'unitname', + zoneId: '9599.1' + }; +}; + +let getDefaultBidRequest = () => { + return { + bidderCode: 'consumable', + auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', + bidderRequestId: '7101db09af0db2', + start: new Date().getTime(), + bids: [{ + bidder: 'consumable', + bidId: '84ab500420319d', + bidderRequestId: '7101db09af0db2', + auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', + placementCode: 'foo', + params: getBidParams() + }] + }; +}; + +let getPixels = () => { + return ''; +}; + +describe('ConsumableAdapter', () => { + const CONSUMABLE_URL = '//adserver-us.adtech.advertising.com/pubapi/3.0/'; + const CONSUMABLE_TTL = 60; + + function createCustomBidRequest({bids, params} = {}) { + var bidderRequest = getDefaultBidRequest(); + if (bids && Array.isArray(bids)) { + bidderRequest.bids = bids; + } + if (params) { + bidderRequest.bids.forEach(bid => bid.params = params); + } + return bidderRequest; + } + + describe('interpretResponse()', () => { + let bidderSettingsBackup; + let bidResponse; + let bidRequest; + let logWarnSpy; + + beforeEach(() => { + bidderSettingsBackup = $$PREBID_GLOBAL$$.bidderSettings; + bidRequest = { + bidderCode: 'test-bidder-code', + bidId: 'bid-id', + unitName: 'unitname', + unitId: '987654', + zoneId: '9599.1', + network: '9599.1' + }; + bidResponse = { + body: getDefaultBidResponse() + }; + logWarnSpy = sinon.spy(utils, 'logWarn'); + }); + + afterEach(() => { + $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsBackup; + logWarnSpy.restore(); + }); + + it('should return formatted bid response with required properties', () => { + let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest); + expect(formattedBidResponse).to.deep.equal({ + bidderCode: bidRequest.bidderCode, + requestId: 'bid-id', + ad: DEFAULT_AD_CONTENT, + cpm: 0.09, + width: 728, + height: 90, + creativeId: 'creative-id', + pubapiId: '245730051428950632', + currency: 'USD', + dealId: 'deal-id', + netRevenue: true, + ttl: 60 + }); + }); + + it('should add formatted pixels to ad content when pixels are present in the response', () => { + bidResponse.body.ext = { + pixels: 'pixels-content' + }; + + let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest); + + expect(formattedBidResponse.ad).to.equal(DEFAULT_AD_CONTENT + ''); + return true; + }); + }); + + describe('buildRequests()', () => { + it('method exists and is a function', () => { + expect(spec.buildRequests).to.exist.and.to.be.a('function'); + }); + + describe('Consumable', () => { + it('should not return request when no bids are present', () => { + let [request] = spec.buildRequests([]); + expect(request).to.be.empty; + }); + + it('should return request for endpoint', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(CONSUMABLE_URL); + }); + + it('should return url with pubapi bid option', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('cmd=bid;'); + }); + + it('should return url with version 2 of pubapi', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain('v=2;'); + }); + + it('should return url with cache busting option', () => { + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.match(/misc=\d+/); + }); + }); + }); + + describe('getUserSyncs()', () => { + let bidResponse; + let bidRequest; + + beforeEach(() => { + $$PREBID_GLOBAL$$.consumableGlobals.pixelsDropped = false; + config.setConfig({ + consumable: { + userSyncOn: 'bidResponse' + }, + }); + bidResponse = getDefaultBidResponse(); + bidResponse.ext = { + pixels: getPixels() + }; + }); + + it('should return user syncs only if userSyncOn equals to "bidResponse"', () => { + let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest); + + expect($$PREBID_GLOBAL$$.consumableGlobals.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', () => { + $$PREBID_GLOBAL$$.consumableGlobals.pixelsDropped = true; + + let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest); + + expect($$PREBID_GLOBAL$$.consumableGlobals.pixelsDropped).to.be.true; + expect(userSyncs).to.deep.equal([]); + }); + + it('should not return user syncs if pixels are not present', () => { + bidResponse.ext.pixels = null; + + let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest); + + expect($$PREBID_GLOBAL$$.consumableGlobals.pixelsDropped).to.be.false; + expect(userSyncs).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/contentigniteBidAdapter_spec.js b/test/spec/modules/contentigniteBidAdapter_spec.js new file mode 100644 index 00000000000..cdf70e15615 --- /dev/null +++ b/test/spec/modules/contentigniteBidAdapter_spec.js @@ -0,0 +1,186 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/contentigniteBidAdapter'; + +describe('Content Ignite adapter', () => { + let bidRequests; + + beforeEach(() => { + bidRequests = [ + { + bidder: 'contentignite', + params: { + accountID: '168237', + zoneID: '299680', + keyword: 'business', + minCPM: '0.10', + maxCPM: '1.00' + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[728, 90]], + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + }); + + describe('implementation', () => { + describe('for requests', () => { + it('should accept valid bid', () => { + const validBid = { + bidder: 'contentignite', + params: { + accountID: '168237', + zoneID: '299680' + } + }, + isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.equal(true); + }); + + it('should reject invalid bid', () => { + const invalidBid = { + bidder: 'contentignite', + params: { + accountID: '168237' + } + }, + isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should set the keyword parameter', () => { + const requests = spec.buildRequests(bidRequests), + requestURL = requests[0].url; + + expect(requestURL).to.have.string(';kw=business;'); + }); + + it('should increment the count for the same zone', () => { + const bidRequests = [ + { + sizes: [[728, 90]], + bidder: 'contentignite', + params: { + accountID: '107878', + zoneID: '86133' + } + }, + { + sizes: [[728, 90]], + bidder: 'contentignite', + params: { + accountID: '107878', + zoneID: '86133' + } + } + ], + requests = spec.buildRequests(bidRequests), + firstRequest = requests[0].url, + secondRequest = requests[1].url; + + expect(firstRequest).to.have.string(';place=0;'); + expect(secondRequest).to.have.string(';place=1;'); + }); + }); + + describe('bid responses', () => { + it('should return complete bid response', () => { + const serverResponse = { + body: { + status: 'SUCCESS', + account_id: 107878, + zone_id: 86133, + cpm: 0.1, + width: 728, + height: 90, + place: 0, + ad_code: + '
', + tracking_pixels: [] + } + }, + bids = spec.interpretResponse(serverResponse, { + bidRequest: bidRequests[0] + }); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].cpm).to.equal(0.1); + 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.have.length.above(1); + }); + + it('should return empty bid response', () => { + const serverResponse = { + status: 'NO_ELIGIBLE_ADS', + zone_id: 299680, + width: 728, + height: 90, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, { + bidRequest: bidRequests[0] + }); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on incorrect size', () => { + const serverResponse = { + status: 'SUCCESS', + account_id: 168237, + zone_id: 299680, + cpm: 0.1, + width: 300, + height: 250, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, { + bidRequest: bidRequests[0] + }); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response with CPM too low', () => { + const serverResponse = { + status: 'SUCCESS', + account_id: 168237, + zone_id: 299680, + cpm: 0.05, + width: 728, + height: 90, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, { + bidRequest: bidRequests[0] + }); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response with CPM too high', () => { + const serverResponse = { + status: 'SUCCESS', + account_id: 168237, + zone_id: 299680, + cpm: 7.0, + width: 728, + height: 90, + place: 0 + }, + bids = spec.interpretResponse(serverResponse, { + bidRequest: bidRequests[0] + }); + + expect(bids).to.be.lengthOf(0); + }); + }); + }); +}); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 57cd9411e66..a8e1b41d966 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -1,376 +1,297 @@ -var expect = require('chai').expect; -var Adapter = require('modules/conversantBidAdapter'); -var bidManager = require('src/bidmanager'); +import {expect} from 'chai'; +import {spec} from 'modules/conversantBidAdapter'; +import * as utils from 'src/utils'; -describe('Conversant adapter tests', function () { - var addBidResponseSpy; - var adapter; +var Adapter = require('modules/conversantBidAdapter'); - var bidderRequest = { - bidderCode: 'conversant', - bids: [ - { - bidId: 'bidId1', - bidder: 'conversant', - placementCode: 'div1', - sizes: [[300, 600]], - params: { - site_id: '87293', - position: 1, - tag_id: 'tagid-1', - secure: false - } - }, { - bidId: 'bidId2', - bidder: 'conversant', - placementCode: 'div2', - sizes: [[300, 600]], - params: { - site_id: '87293', - secure: false - } - }, { - bidId: 'bidId3', - bidder: 'conversant', - placementCode: 'div3', - sizes: [[300, 600], [160, 600]], - params: { - site_id: '87293', - position: 1, - tag_id: '', - secure: false - } - }, { - bidId: 'bidId4', - bidder: 'conversant', - placementCode: 'div4', - mediaType: 'video', - sizes: [[480, 480]], - params: { - site_id: '89192', - pos: 1, - tagid: 'tagid-4', - secure: false +describe('Conversant adapter tests', function() { + const siteId = '108060'; + + const bidRequests = [ + { + bidder: 'conversant', + params: { + site_id: siteId, + position: 1, + tag_id: 'tagid-1', + secure: false, + bidfloor: 0.5 + }, + placementCode: 'pcode000', + transactionId: 'tx000', + sizes: [[300, 250]], + bidId: 'bid000', + bidderRequestId: '117d765b87bed38', + auctionId: 'req000' + }, { + bidder: 'conversant', + params: { + site_id: siteId, + secure: false + }, + placementCode: 'pcode001', + transactionId: 'tx001', + sizes: [[468, 60]], + bidId: 'bid001', + bidderRequestId: '117d765b87bed38', + auctionId: 'req000' + }, { + bidder: 'conversant', + params: { + site_id: siteId, + position: 2, + tag_id: '', + secure: false + }, + placementCode: 'pcode002', + transactionId: 'tx002', + sizes: [[300, 600], [160, 600]], + bidId: 'bid002', + bidderRequestId: '117d765b87bed38', + auctionId: 'req000' + }, { + bidder: 'conversant', + params: { + site_id: siteId, + api: [2], + protocols: [1, 2], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30 + }, + mediaTypes: { + video: { + context: 'instream' } - } - ] - }; - - it('The Conversant response should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.conversantResponse).to.exist.and.to.be.a('function'); - }); - - describe('Should submit bid responses correctly', function () { - beforeEach(function () { - addBidResponseSpy = sinon.stub(bidManager, 'addBidResponse'); - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter = new Adapter(); - }); - - afterEach(function () { - addBidResponseSpy.restore(); - }); - - it('Should correctly submit valid and empty bids to the bid manager', function () { - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111111, - impid: 'bidId1', - price: 0 - }, { - id: 2345, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 - }] - }] - }; - - $$PREBID_GLOBAL$$.conversantResponse(bidResponse); - - // in this case, the valid bid (div2) is submitted before the empty bids (div1, div3) - var firstBid = addBidResponseSpy.getCall(0).args[1]; - var secondBid = addBidResponseSpy.getCall(1).args[1]; - var thirdBid = addBidResponseSpy.getCall(2).args[1]; - var placementCode1 = addBidResponseSpy.getCall(0).args[0]; - var placementCode2 = addBidResponseSpy.getCall(1).args[0]; - var placementCode3 = addBidResponseSpy.getCall(2).args[0]; - - expect(firstBid.getStatusCode()).to.equal(1); - expect(firstBid.bidderCode).to.equal('conversant'); - expect(firstBid.cpm).to.equal(0.22); - expect(firstBid.ad).to.equal('adm2' + ''); - expect(placementCode1).to.equal('div2'); - - expect(secondBid.getStatusCode()).to.equal(2); - expect(secondBid.bidderCode).to.equal('conversant'); - expect(placementCode2).to.equal('div1'); - - expect(thirdBid.getStatusCode()).to.equal(2); - expect(thirdBid.bidderCode).to.equal('conversant'); - expect(placementCode3).to.equal('div3'); - - expect(addBidResponseSpy.getCalls().length).to.equal(4); - }); - - it('Should submit bids with statuses of 2 to the bid manager for empty bid responses', function () { - $$PREBID_GLOBAL$$.conversantResponse({id: 1, seatbid: []}); - - var placementCode1 = addBidResponseSpy.getCall(0).args[0]; - var firstBid = addBidResponseSpy.getCall(0).args[1]; - var placementCode2 = addBidResponseSpy.getCall(1).args[0]; - var secondBid = addBidResponseSpy.getCall(1).args[1]; - var placementCode3 = addBidResponseSpy.getCall(2).args[0]; - var thirdBid = addBidResponseSpy.getCall(2).args[1]; - - expect(placementCode1).to.equal('div1'); - expect(firstBid.getStatusCode()).to.equal(2); - expect(firstBid.bidderCode).to.equal('conversant'); - - expect(placementCode2).to.equal('div2'); - expect(secondBid.getStatusCode()).to.equal(2); - expect(secondBid.bidderCode).to.equal('conversant'); - - expect(placementCode3).to.equal('div3'); - expect(thirdBid.getStatusCode()).to.equal(2); - expect(thirdBid.bidderCode).to.equal('conversant'); - - expect(addBidResponseSpy.getCalls().length).to.equal(4); - }); - - it('Should submit valid bids to the bid manager', function () { - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111111, - impid: 'bidId1', - price: 0.11, - nurl: '', - adm: 'adm', - h: 250, - w: 300, - ext: {} - }, { - id: 2345, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 - }, { - id: 33333, - impid: 'bidId3', - price: 0.33, - nurl: '', - adm: 'adm3', - h: 160, - w: 600 - }] - }] - }; - - $$PREBID_GLOBAL$$.conversantResponse(bidResponse); - - var firstBid = addBidResponseSpy.getCall(0).args[1]; - var secondBid = addBidResponseSpy.getCall(1).args[1]; - var thirdBid = addBidResponseSpy.getCall(2).args[1]; - var placementCode1 = addBidResponseSpy.getCall(0).args[0]; - var placementCode2 = addBidResponseSpy.getCall(1).args[0]; - var placementCode3 = addBidResponseSpy.getCall(2).args[0]; - - expect(firstBid.getStatusCode()).to.equal(1); - expect(firstBid.bidderCode).to.equal('conversant'); - expect(firstBid.cpm).to.equal(0.11); - expect(firstBid.ad).to.equal('adm' + ''); - expect(placementCode1).to.equal('div1'); - - expect(secondBid.getStatusCode()).to.equal(1); - expect(secondBid.bidderCode).to.equal('conversant'); - expect(secondBid.cpm).to.equal(0.22); - expect(secondBid.ad).to.equal('adm2' + ''); - expect(placementCode2).to.equal('div2'); - - expect(thirdBid.getStatusCode()).to.equal(1); - expect(thirdBid.bidderCode).to.equal('conversant'); - expect(thirdBid.cpm).to.equal(0.33); - expect(thirdBid.ad).to.equal('adm3' + ''); - expect(placementCode3).to.equal('div3'); - - expect(addBidResponseSpy.getCalls().length).to.equal(4); - }); - - it('Should submit video bid responses correctly.', function () { - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111111, - impid: 'bidId4', - price: 0.11, - nurl: 'imp_tracker', - adm: 'vasturl' - }] - }] - }; - - $$PREBID_GLOBAL$$.conversantResponse(bidResponse); - - var videoBid = addBidResponseSpy.getCall(0).args[1]; - var placementCode = addBidResponseSpy.getCall(0).args[0]; - - expect(videoBid.getStatusCode()).to.equal(1); - expect(videoBid.bidderCode).to.equal('conversant'); - expect(videoBid.cpm).to.equal(0.11); - expect(videoBid.vastUrl).to.equal('vasturl'); - expect(placementCode).to.equal('div4'); - }) - }); - - describe('Should submit the correct headers in the xhr', function () { - var server, - adapter; - - var bidResponse = { - id: 123, + }, + placementCode: 'pcode003', + transactionId: 'tx003', + sizes: [640, 480], + bidId: 'bid003', + bidderRequestId: '117d765b87bed38', + auctionId: 'req000' + }]; + + const bidResponses = { + body: { + id: 'req000', seatbid: [{ bid: [{ - id: 1111, - impid: 'bidId1', - price: 0.11, - nurl: '', - adm: 'adm', - h: 250, + nurl: 'notify000', + adm: 'markup000', + crid: '1000', + impid: 'bid000', + price: 0.99, w: 300, - ext: {} + h: 250, + adomain: ['https://example.com'], + id: 'bid000' }, { - id: 2222, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 + impid: 'bid001', + price: 0.00000, + id: 'bid001' }, { - id: 3333, - impid: 'bidId3', - price: 0.33, - nurl: '', - adm: 'adm3', - h: 160, - w: 600 - }] - }] - }; - - beforeEach(function () { - server = sinon.fakeServer.create(); - adapter = new Adapter(); - }); - - afterEach(function () { - server.restore(); - }); - - beforeEach(function () { - var resp = [200, {'Content-type': 'text/javascript'}, '$$PREBID_GLOBAL$$.conversantResponse(\'' + JSON.stringify(bidResponse) + '\')']; - server.respondWith('POST', new RegExp('media.msg.dotomi.com/s2s/header'), resp); - }); - - it('Should contain valid request header properties', function () { - adapter.callBids(bidderRequest); - server.respond(); - - var request = server.requests[0]; - expect(request.requestBody).to.not.be.empty; - }); - }); - describe('Should create valid bid requests.', function () { - var server, - adapter; - - var bidResponse = { - id: 123, - seatbid: [{ - bid: [{ - id: 1111, - impid: 'bidId1', - price: 0.11, - nurl: '', - adm: 'adm', - h: 250, + nurl: 'notify002', + adm: 'markup002', + crid: '1002', + impid: 'bid002', + price: 2.99, w: 300, - ext: {} - }, { - id: 2222, - impid: 'bidId2', - price: 0.22, - nurl: '', - adm: 'adm2', - h: 300, - w: 600 + h: 600, + adomain: ['https://example.com'], + id: 'bid002' }, { - id: 3333, - impid: 'bidId3', - price: 0.33, - nurl: '', - adm: 'adm3', - h: 160, - w: 600 + nurl: 'notify003', + adm: 'markup003', + crid: '1003', + impid: 'bid003', + price: 3.99, + adomain: ['https://example.com'], + id: 'bid003' }] }] - }; + }, + headers: {}}; + + it('Verify basic properties', function() { + expect(spec.code).to.equal('conversant'); + expect(spec.aliases).to.be.an('array').with.lengthOf(1); + expect(spec.aliases[0]).to.equal('cnvr'); + expect(spec.supportedMediaTypes).to.be.an('array').with.lengthOf(2); + expect(spec.supportedMediaTypes[1]).to.equal('video'); + }); - beforeEach(function () { - server = sinon.fakeServer.create(); - adapter = new Adapter(); - }); + it('Verify user syncs', function() { + expect(spec.getUserSyncs({})).to.be.undefined; + expect(spec.getUserSyncs({iframeEnabled: true})).to.be.undefined; + expect(spec.getUserSyncs({pixelEnabled: false})).to.be.undefined; - afterEach(function () { - server.restore(); - }); + const syncs = spec.getUserSyncs({pixelEnabled: true}); + expect(syncs).to.be.an('array').with.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('//media.msg.dotomi.com/w/user.sync'); + }); - beforeEach(function () { - var resp = [200, {'Content-type': 'text/javascript'}, '$$PREBID_GLOBAL$$.conversantResponse(\'' + JSON.stringify(bidResponse) + '\')']; - server.respondWith('POST', new RegExp('media.msg.dotomi.com/s2s/header'), resp); - }); + it('Verify isBidRequestValid', function() { + expect(spec.isBidRequestValid({})).to.be.false; + expect(spec.isBidRequestValid({params: {}})).to.be.false; + expect(spec.isBidRequestValid({params: {site_id: '123'}})).to.be.true; + expect(spec.isBidRequestValid(bidRequests[0])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[1])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[2])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[3])).to.be.true; + + const simpleVideo = JSON.parse(JSON.stringify(bidRequests[3])); + simpleVideo.params.site_id = 123; + expect(spec.isBidRequestValid(simpleVideo)).to.be.false; + simpleVideo.params.site_id = siteId; + simpleVideo.params.mimes = [1, 2, 3]; + expect(spec.isBidRequestValid(simpleVideo)).to.be.false; + simpleVideo.params.mimes = 'bad type'; + expect(spec.isBidRequestValid(simpleVideo)).to.be.false; + delete simpleVideo.params.mimes; + expect(spec.isBidRequestValid(simpleVideo)).to.be.true; + }); - it('Should create valid bid requests.', function () { - adapter.callBids(bidderRequest); - server.respond(); - var request = JSON.parse(server.requests[0].requestBody); - expect(request.imp[0].banner.format[0].w).to.equal(300); - expect(request.imp[0].banner.format[0].h).to.equal(600); - expect(request.imp[0].tagid).to.equal('tagid-1'); - expect(request.imp[0].banner.pos).to.equal(1); - expect(request.imp[0].secure).to.equal(0); - expect(request.site.id).to.equal('89192'); - }); + it('Verify buildRequest', function() { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('//media.msg.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[0]).to.have.property('id', 'bid000'); + expect(payload.imp[0]).to.have.property('secure', 0); + 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('tagid', 'tagid-1'); + expect(payload.imp[0]).to.have.property('banner'); + expect(payload.imp[0].banner).to.have.property('pos', 1); + expect(payload.imp[0].banner).to.have.property('format'); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + 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('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.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[2]).to.have.property('id', 'bid002'); + expect(payload.imp[2]).to.have.property('secure', 0); + 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('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('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.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('mimes'); + expect(payload.imp[3].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(payload.imp[3].video).to.have.property('protocols'); + expect(payload.imp[3].video.protocols).to.deep.equal([1, 2]); + expect(payload.imp[3].video).to.have.property('api'); + expect(payload.imp[3].video.api).to.deep.equal([2]); + expect(payload.imp[3].video).to.have.property('maxduration', 30); + expect(payload.imp[3]).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'); + expect(payload.device).to.have.property('w', screen.width); + expect(payload.device).to.have.property('h', screen.height); + expect(payload.device).to.have.property('dnt').that.is.oneOf([0, 1]); + expect(payload.device).to.have.property('ua', navigator.userAgent); + + expect(payload).to.not.have.property('user'); // there should be no user by default + }); - it('Should not pass empty or missing optional parameters on requests.', function () { - adapter.callBids(bidderRequest); - server.respond(); + it('Verify interpretResponse', function() { + const request = spec.buildRequests(bidRequests); + const response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.lengthOf(3); + + let bid = response[0]; + expect(bid).to.have.property('requestId', 'bid000'); + expect(bid).to.have.property('currency', 'USD'); + expect(bid).to.have.property('cpm', 0.99); + expect(bid).to.have.property('creativeId', '1000'); + expect(bid).to.have.property('width', 300); + expect(bid).to.have.property('height', 250); + expect(bid).to.have.property('ad', 'markup000'); + expect(bid).to.have.property('ttl', 300); + expect(bid).to.have.property('netRevenue', true); + + // There is no bid001 because cpm is $0 + + bid = response[1]; + expect(bid).to.have.property('requestId', 'bid002'); + expect(bid).to.have.property('currency', 'USD'); + expect(bid).to.have.property('cpm', 2.99); + expect(bid).to.have.property('creativeId', '1002'); + expect(bid).to.have.property('width', 300); + expect(bid).to.have.property('height', 600); + expect(bid).to.have.property('ad', 'markup002'); + expect(bid).to.have.property('ttl', 300); + expect(bid).to.have.property('netRevenue', true); + + bid = response[2]; + expect(bid).to.have.property('requestId', 'bid003'); + 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('vastUrl', 'markup003'); + expect(bid).to.have.property('mediaType', 'video'); + expect(bid).to.have.property('ttl', 300); + expect(bid).to.have.property('netRevenue', true); + }); - var request = JSON.parse(server.requests[0].requestBody); - expect(request.imp[1].tagid).to.equal(undefined); - expect(request.imp[2].tagid).to.equal(undefined); - expect(request.imp[1].pos).to.equal(undefined); - }); + it('Verify handling of bad responses', function() { + let response = spec.interpretResponse({}, {}); + expect(response).to.be.an('array').with.lengthOf(0); + response = spec.interpretResponse({id: '123'}, {}); + expect(response).to.be.an('array').with.lengthOf(0); + response = spec.interpretResponse({id: '123', seatbid: []}, {}); + expect(response).to.be.an('array').with.lengthOf(0); + }); - it('Should create the format objects correctly.', function () { - adapter.callBids(bidderRequest); - server.respond(); + it('Verify publisher commond id support', function() { + // clone bidRequests + let requests = utils.deepClone(bidRequests) - var request = JSON.parse(server.requests[0].requestBody); - expect(request.imp[2].banner.format.length).to.equal(2); - expect(request.imp[2].banner.format[0].w).to.equal(300); - expect(request.imp[2].banner.format[1].w).to.equal(160); + // add pubcid to every entry + requests.forEach((unit) => { + Object.assign(unit, {crumbs: {pubcid: 12345}}); }); + // construct http post payload + const payload = spec.buildRequests(requests).data; + expect(payload).to.have.deep.property('user.ext.fpc', 12345); }); -}); +}) diff --git a/test/spec/modules/coxBidAdapter_spec.js b/test/spec/modules/coxBidAdapter_spec.js deleted file mode 100644 index ee1eb991f23..00000000000 --- a/test/spec/modules/coxBidAdapter_spec.js +++ /dev/null @@ -1,120 +0,0 @@ -import Adapter from 'modules/coxBidAdapter'; -import bidManager from 'src/bidmanager'; -import adLoader from 'src/adloader'; -import {expect} from 'chai'; - -describe('CoxAdapter', () => { - let adapter; - let loadScriptStub; - let addBidResponseSpy; - - let emitScript = (script) => { - let node = document.createElement('script'); - node.type = 'text/javascript'; - node.appendChild(document.createTextNode(script)); - document.getElementsByTagName('head')[0].appendChild(node); - }; - - beforeEach(() => { - adapter = new Adapter(); - addBidResponseSpy = sinon.spy(bidManager, 'addBidResponse'); - }); - - afterEach(() => { - loadScriptStub.restore(); - addBidResponseSpy.restore(); - }); - - describe('response handling', () => { - const normalResponse = 'cdsTag.__callback__({"zones":{"as2000005991707":{"ad" : "

FOO<\/h1>","uid" : "","price" : 1.51,"floor" : 0,}},"tpCookieSync":"

FOOKIE<\/h1>"})'; - const zeroPriceResponse = 'cdsTag.__callback__({"zones":{"as2000005991707":{"ad" : "

DEFAULT FOO<\/h1>","uid" : "","price" : 0,"floor" : 0,}},"tpCookieSync":"

FOOKIE<\/h1>"})'; - const incompleteResponse = 'cdsTag.__callback__({"zones":{},"tpCookieSync":"

FOOKIE<\/h1>"})'; - - const oneBidConfig = { - bidderCode: 'cox', - bids: [{ - bidder: 'cox', - placementCode: 'FOO456789', - sizes: [300, 250], - params: { size: '300x250', id: 2000005991707, siteId: 2000100948180, env: 'PROD' }, - }] - }; - - // ===== 1 - it('should provide a correctly populated Bid given a valid response', () => { - loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(normalResponse); }) - - adapter.callBids(oneBidConfig); - - let bid = addBidResponseSpy.args[0][1]; - expect(bid.cpm).to.equal(1.51); - expect(bid.ad).to.be.a('string'); - expect(bid.bidderCode).to.equal('cox'); - }); - - // ===== 2 - it('should provide an empty Bid given a zero-price response', () => { - loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(zeroPriceResponse); }) - - adapter.callBids(oneBidConfig); - - let bid = addBidResponseSpy.args[0][1]; - expect(bid.cpm).to.not.be.ok - expect(bid.ad).to.not.be.ok; - }); - - // ===== 3 - it('should provide an empty Bid given an incomplete response', () => { - loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(incompleteResponse); }) - - adapter.callBids(oneBidConfig); - - let bid = addBidResponseSpy.args[0][1]; - expect(bid.cpm).to.not.be.ok - expect(bid.ad).to.not.be.ok; - }); - - // ===== 4 - it('should not provide a Bid given no response', () => { - loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(''); }); - - adapter.callBids(oneBidConfig); - - expect(addBidResponseSpy.callCount).to.equal(0); - }); - }); - - describe('request generation', () => { - const missingBidsConfig = { - bidderCode: 'cox', - bids: null, - }; - const missingParamsConfig = { - bidderCode: 'cox', - bids: [{ - bidder: 'cox', - placementCode: 'FOO456789', - sizes: [300, 250], - params: null, - }] - }; - - // ===== 5 - it('should not make an ad call given missing bids in config', () => { - loadScriptStub = sinon.stub(adLoader, 'loadScript'); - - adapter.callBids(missingBidsConfig); - - expect(loadScriptStub.callCount).to.equal(0); - }); - - // ===== 6 - it('should not make an ad call given missing params in config', () => { - loadScriptStub = sinon.stub(adLoader, 'loadScript'); - - adapter.callBids(missingParamsConfig); - - expect(loadScriptStub.callCount).to.equal(0); - }); - }); -}); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js deleted file mode 100644 index 68c554c7cb4..00000000000 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ /dev/null @@ -1,274 +0,0 @@ -import Adapter from '../../../modules/criteoBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import { ajax } from '../../../src/ajax' -import { expect } from 'chai'; - -var CONSTANTS = require('../../../src/constants'); - -/* ------------ Publishertag stub begin ------------ */ -before(() => { - window.Criteo = { - PubTag: { - DirectBidding: { - DirectBiddingSlot: function DirectBiddingSlot(placementCode, zoneid, nativeCallback, transactionId, sizes) { - return { - impId: placementCode, - nativeCallback: nativeCallback - }; - }, - - DirectBiddingUrlBuilder: function DirectBiddingUrlBuilder(isAudit) { return {} }, - - DirectBiddingEvent: function DirectBiddingEvent(profileId, urlBuilder, slots, success, error, timeout) { - return { - slots: slots, - eval: function () { - var callbacks = { - error: error, - success: success - } - ajax('//bidder.criteo.com/cdb', callbacks) - } - } - }, - - Size: function Size(width, height) { return {width: width, height: height} } - } - } - }; - - window.criteo_pubtag = window.criteo_pubtag || { - push: function (event) { - event.eval(); - } - } - - window.Criteo.events = window.Criteo.events || []; - window.Criteo.events.push = function (elem) { - if (typeof elem === 'function') { - elem(); - } - }; -}); -/* ------------ Publishertag stub end ------------ */ - -describe('criteo adapter test', () => { - let adapter; - let stubAddBidResponse; - - let validBid = { - bidderCode: 'criteo', - bids: [ - { - bidder: 'criteo', - placementCode: 'foo', - sizes: [[250, 350]], - params: { - zoneId: 32934, - audit: 'true' - } - } - ] - }; - - let validResponse = { slots: [{ impid: 'foo', cpm: 1.12, creative: "" }] }; - let invalidResponse = { slots: [{ 'impid': 'unknownSlot' }] } - - let validMultiBid = { - bidderCode: 'criteo', - bids: [ - { - bidder: 'criteo', - placementCode: 'foo', - sizes: [[250, 350]], - params: { - zoneId: 32934, - audit: 'true' - } - }, - { - bidder: 'criteo', - placementCode: 'bar', - sizes: [[250, 350]], - params: { - zoneId: 32935, - audit: 'true' - } - } - ] - }; - - let validNativeResponse = { slots: [{ impid: 'foo', cpm: 1.12, native: { productName: 'product0' } }] }; - let validNativeBid = { - bidderCode: 'criteo', - bids: [ - { - bidder: 'criteo', - placementCode: 'foo', - sizes: [[250, 350]], - params: { - zoneId: 32934, - audit: 'true', - nativeCallback: function (nativeJson) { console.log('Product name: ' + nativeJson.productName) } - } - } - ] - } - - beforeEach(() => { - adapter = new Adapter(); - }); - - afterEach(() => { - stubAddBidResponse.restore(); - }); - - describe('adding bids to the manager', () => { - let server; - - beforeEach(() => { - server = sinon.fakeServer.create({ autoRespond: true, respondImmediately: true }); - server.respondWith(JSON.stringify(validResponse)); - }); - - afterEach(() => { - server.restore(); - }); - - it('adds bid for valid request', (done) => { - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.GOOD }); - done(); - }); - - adapter.callBids(validBid); - }); - - it('adds bid for multibid valid request', (done) => { - let callCount = 0; - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - callCount++; - - if (callCount == 2) { done(); } - }); - - adapter.callBids(validMultiBid); - }); - - it('adds bidderCode to the response of a valid request', (done) => { - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - expect(bid).to.have.property('bidderCode', 'criteo'); - done(); - }); - - adapter.callBids(validBid); - }); - - it('adds cpm to the response of a valid request', (done) => { - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - expect(bid).to.have.property('cpm', 1.12); - done(); - }); - adapter.callBids(validBid); - }); - - it('adds creative to the response of a valid request', (done) => { - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - expect(bid).to.have.property('ad', ""); - done(); - }); - adapter.callBids(validBid); - }); - }); - - describe('adding bids to the manager with native bids', () => { - let server; - - beforeEach(() => { - server = sinon.fakeServer.create({ autoRespond: true, respondImmediately: true }); - server.respondWith(JSON.stringify(validNativeResponse)); - }); - - afterEach(() => { - server.restore(); - }); - - it('adds creative to the response of a native valid request', (done) => { - stubAddBidResponse = sinon.stub( - bidManager, 'addBidResponse', - function (adUnitCode, bid) { - let expectedAdProperty = ``; - - expect(bid).to.have.property('ad', expectedAdProperty); - done(); - }); - adapter.callBids(validNativeBid); - }); - }); - - describe('dealing with unexpected situations', () => { - let server; - - beforeEach(() => { - server = sinon.fakeServer.create({ autoRespond: true, respondImmediately: true }); - }); - - afterEach(() => { - server.restore(); - }); - - it('no bid if cdb handler responds with no bid empty string response', (done) => { - server.respondWith(''); - - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.NO_BID }); - done(); - }); - - adapter.callBids(validBid); - }); - - it('no bid if cdb handler responds with no bid empty object response', (done) => { - server.respondWith('{ }'); - - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.NO_BID }); - done(); - }); - - adapter.callBids(validBid); - }); - - it('no bid if cdb handler responds with HTTP error', (done) => { - server.respondWith([500, {}, 'Internal Server Error']); - - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.NO_BID }); - done(); - }); - - adapter.callBids(validBid); - }); - - it('no bid if response is invalid because response slots don\'t match input slots', (done) => { - server.respondWith(JSON.stringify(invalidResponse)); - - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.NO_BID }); - done(); - }); - - adapter.callBids(validBid); - }); - }); -}); diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index 937e6a084e4..a4ef643fece 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -5,17 +5,22 @@ import { import { setConfig, - addBidResponseDecorator, - + addBidResponseHook, currencySupportEnabled, 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 fn = sinon.spy(); + let hookFn = createHook('asyncSeries', fn, 'addBidResponse'); + beforeEach(() => { fakeCurrencyFileServer = sinon.fakeServer.create(); }); @@ -46,10 +51,6 @@ describe('currency', function () { var bid = { cpm: 1, bidder: 'rubicon' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { - innerBid = bid; - }); - setConfig({ adServerCurrency: 'GBP', bidderCurrencyDefault: { @@ -57,7 +58,9 @@ describe('currency', function () { } }); - wrappedAddBidResponseFn('elementId', bid); + addBidResponseHook('elementId', bid, function(adCodeId, bid) { + innerBid = bid; + }); expect(innerBid.currency).to.equal('GBP') }); @@ -68,10 +71,6 @@ describe('currency', function () { var bid = { cpm: 1, currency: 'JPY', bidder: 'rubicon' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { - innerBid = bid; - }); - setConfig({ adServerCurrency: 'JPY', bidderCurrencyDefault: { @@ -79,7 +78,9 @@ describe('currency', function () { } }); - wrappedAddBidResponseFn('elementId', bid); + addBidResponseHook('elementId', bid, function(adCodeId, bid) { + innerBid = bid; + }); expect(innerBid.currency).to.equal('JPY') }); @@ -97,11 +98,34 @@ describe('currency', function () { var bid = { cpm: 100, currency: 'JPY', bidder: 'rubicon' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); + expect(innerBid.cpm).to.equal('1.0000'); + }); + + it('uses default rates when currency file fails to load', () => { + setConfig({}); + + setConfig({ + adServerCurrency: 'USD', + defaultRates: { + USD: { + JPY: 100 + } + } + }); + + // default response is 404 + fakeCurrencyFileServer.respond(); + + var bid = { cpm: 100, currency: 'JPY', bidder: 'rubicon' }; + var innerBid; + + addBidResponseHook('elementId', bid, function(adCodeId, bid) { + innerBid = bid; + }); expect(innerBid.cpm).to.equal('1.0000'); }); @@ -113,14 +137,15 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); - var marker = false; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { - marker = true; - }); var bid = { 'cpm': 1, 'currency': 'USD' }; setConfig({ 'adServerCurrency': 'JPY' }); - wrappedAddBidResponseFn('elementId', bid); + + var marker = false; + addBidResponseHook('elementId', bid, function() { + marker = true; + }); + expect(marker).to.equal(false); fakeCurrencyFileServer.respond(); @@ -133,10 +158,9 @@ describe('currency', function () { setConfig({}); var bid = { 'cpm': 1, 'currency': 'USD' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.cpm).to.equal(1); }); @@ -144,10 +168,9 @@ describe('currency', function () { setConfig({}); var bid = { 'cpm': 1, 'currency': 'GBP' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); }); @@ -157,10 +180,9 @@ describe('currency', function () { }); var bid = { 'cpm': 1, 'currency': 'USD' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(bid).to.equal(innerBid); }); @@ -170,10 +192,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'ABC' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); }); @@ -183,10 +204,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'GBP' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); }); @@ -196,10 +216,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'JPY' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.cpm).to.equal(1); expect(innerBid.currency).to.equal('JPY'); }); @@ -210,10 +229,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'USD' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.cpm).to.equal('0.7798'); expect(innerBid.currency).to.equal('GBP'); }); @@ -224,10 +242,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'CNY' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.cpm).to.equal('0.1133'); expect(innerBid.currency).to.equal('GBP'); }); @@ -238,10 +255,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'JPY' }; var innerBid; - var wrappedAddBidResponseFn = addBidResponseDecorator(function(adCodeId, bid) { + addBidResponseHook('elementId', bid, function(adCodeId, bid) { innerBid = bid; }); - wrappedAddBidResponseFn('elementId', bid); expect(innerBid.cpm).to.equal('0.0623'); expect(innerBid.currency).to.equal('CNY'); }); diff --git a/test/spec/modules/danmarketBidAdapter_spec.js b/test/spec/modules/danmarketBidAdapter_spec.js new file mode 100644 index 00000000000..d1b86d1bf53 --- /dev/null +++ b/test/spec/modules/danmarketBidAdapter_spec.js @@ -0,0 +1,285 @@ +import { expect } from 'chai'; +import { spec } from 'modules/danmarketBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('DAN_Marketplace Adapter', function () { + 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', () => { + let bid = { + 'bidder': 'danmarket', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + 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', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'danmarket', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'danmarket', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'danmarket', + 'params': { + 'uid': '6' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', () => { + const request = spec.buildRequests([bidRequests[0]]); + 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', 'net'); + expect(payload).to.have.property('auids', '5'); + }); + + it('auids must not be duplicated', () => { + const request = spec.buildRequests(bidRequests); + 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', 'net'); + expect(payload).to.have.property('auids', '5,6'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', () => { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests); + 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', '5,6'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', () => { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests); + 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', 'net'); + expect(payload).to.have.property('auids', '5,6'); + delete bidRequests[1].params.priceType; + }); + }); + + describe('interpretResponse', () => { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 4, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 5, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 6, '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', () => { + const bidRequests = [ + { + 'bidder': 'danmarket', + 'params': { + 'uid': '4' + }, + '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': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + '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', () => { + const bidRequests = [ + { + 'bidder': 'danmarket', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'danmarket', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'danmarket', + 'params': { + 'uid': '4' + }, + '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': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 5, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
test content 2
', + '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', () => { + const bidRequests = [ + { + 'bidder': 'danmarket', + 'params': { + 'uid': '6' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'danmarket', + 'params': { + 'uid': '7' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'danmarket', + 'params': { + 'uid': '8' + }, + '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/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 81c83baa65c..c4a799b0b0a 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -36,6 +36,43 @@ describe('The DFP video support module', () => { expect(queryParams).to.have.property('url'); }); + it('can take an adserver url as a parameter', () => { + const bidCopy = Object.assign({ }, bid); + bidCopy.vastUrl = 'vastUrl.example'; + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + url: 'https://video.adserver.example/', + })); + + expect(url.host).to.equal('video.adserver.example'); + + const queryObject = parseQS(url.query); + expect(queryObject.description_url).to.equal('vastUrl.example'); + }); + + it('requires a params object or url', () => { + const url = buildDfpVideoUrl({ + adUnit: adUnit, + bid: bid, + }); + + expect(url).to.be.undefined; + }); + + it('overwrites url params when both url and params object are given', () => { + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bid, + url: 'https://video.adserver.example/ads?sz=640x480&iu=/123/aduniturl&impl=s', + params: { iu: 'my/adUnit' } + })); + + const queryObject = parseQS(url.query); + expect(queryObject.iu).to.equal('my/adUnit'); + }); + it('should override param defaults with user-provided ones', () => { const url = parse(buildDfpVideoUrl({ adUnit: adUnit, @@ -67,6 +104,7 @@ describe('The DFP video support module', () => { expect(customParams).to.have.property('hb_adid', 'ad_id'); expect(customParams).to.have.property('hb_uuid', bid.videoCacheKey); + expect(customParams).to.have.property('hb_cache_id', bid.videoCacheKey); }); it('should merge the user-provided cust_params with the default ones', () => { @@ -91,4 +129,52 @@ describe('The DFP video support module', () => { expect(customParams).to.have.property('hb_adid', 'ad_id'); expect(customParams).to.have.property('my_targeting', 'foo'); }); + + it('should merge the user-provided cust-params with the default ones when using url object', () => { + const bidCopy = Object.assign({ }, bid); + bidCopy.adserverTargeting = { + hb_adid: 'ad_id', + }; + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + url: 'https://video.adserver.example/ads?sz=640x480&iu=/123/aduniturl&impl=s&cust_params=section%3dblog%26mykey%3dmyvalue' + })); + + const queryObject = parseQS(url.query); + const customParams = parseQS('?' + decodeURIComponent(queryObject.cust_params)); + + expect(customParams).to.have.property('hb_adid', 'ad_id'); + expect(customParams).to.have.property('section', 'blog'); + expect(customParams).to.have.property('mykey', 'myvalue'); + expect(customParams).to.have.property('hb_uuid', 'abc'); + expect(customParams).to.have.property('hb_cache_id', 'abc'); + }); + + it('should not overwrite an existing description_url for object input and cache disabled', () => { + const bidCopy = Object.assign({}, bid); + bidCopy.vastUrl = 'vastUrl.example'; + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + iu: 'my/adUnit', + description_url: 'descriptionurl.example' + } + })); + + const queryObject = parseQS(url.query); + expect(queryObject.description_url).to.equal('descriptionurl.example'); + }); + + it('should work with nobid responses', () => { + const url = buildDfpVideoUrl({ + adUnit: adUnit, + params: { 'iu': 'my/adUnit' } + }); + + expect(url).to.be.a('string'); + }); }); diff --git a/test/spec/modules/dgadsBidAdapter_spec.js b/test/spec/modules/dgadsBidAdapter_spec.js new file mode 100644 index 00000000000..89affd94880 --- /dev/null +++ b/test/spec/modules/dgadsBidAdapter_spec.js @@ -0,0 +1,291 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils'; +import {spec} from 'modules/dgadsBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import { BANNER, NATIVE } from 'src/mediaTypes'; + +describe('dgadsBidAdapter', () => { + const adapter = newBidder(spec); + const VALID_ENDPOINT = 'https://ads-tr.bigmining.com/ad/p/bid'; + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'dgads', + params: { + site_id: '1', + location_id: '1' + } + }; + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params(location_id) are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + site_id: '1' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required params(site_id) are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + location_id: '1' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const bidRequests = [ + { // banner + bidder: 'dgads', + mediaType: 'banner', + params: { + site_id: '1', + location_id: '1' + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '2db3101abaec66', + bidderRequestId: '14a9f773e30243', + auctionId: 'c0cd37c5-af11-464d-b83e-35863e533b1f', + transactionId: 'c1f1eff6-23c6-4844-a321-575212939e37' + }, + { // native + bidder: 'dgads', + sizes: [[300, 250]], + params: { + site_id: '1', + location_id: '10' + }, + mediaTypes: { + native: { + image: { + required: true + }, + title: { + required: true, + len: 25 + }, + clickUrl: { + required: true + }, + body: { + required: true, + len: 140 + }, + sponsoredBy: { + required: true, + len: 40 + } + }, + }, + adUnitCode: 'adunit-code', + bidId: '2db3101abaec66', + bidderRequestId: '14a9f773e30243', + auctionId: 'c0cd37c5-af11-464d-b83e-35863e533b1f', + transactionId: 'c1f1eff6-23c6-4844-a321-575212939e37' + } + ]; + it('no bidRequests', () => { + const noBidRequests = []; + expect(Object.keys(spec.buildRequests(noBidRequests)).length).to.equal(0); + }); + const data = { + location_id: '1', + site_id: '1', + transaction_id: 'c1f1eff6-23c6-4844-a321-575212939e37', + bid_id: '2db3101abaec66' + }; + it('sends bid request to VALID_ENDPOINT via POST', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(VALID_ENDPOINT); + expect(request.method).to.equal('POST'); + }); + it('should attache params to the request', () => { + 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['transaction_id']).to.equal(data['transaction_id']); + expect(request.data['bid_id']).to.equal(data['bid_id']); + }); + }); + + describe('interpretResponse', () => { + const bidRequests = { + banner: { + bidRequest: { + bidder: 'dgads', + params: { + location_id: '1', + site_id: '1' + }, + transactionId: 'c1f1eff6-23c6-4844-a321-575212939e37', + bidId: '2db3101abaec66', + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidderRequestId: '14a9f773e30243', + auctionId: 'c0cd37c5-af11-464d-b83e-35863e533b1f' + }, + }, + native: { + bidRequest: { + bidder: 'adg', + params: { + site_id: '1', + location_id: '10' + }, + mediaTypes: { + native: { + image: { + required: true + }, + title: { + required: true, + len: 25 + }, + body: { + required: true, + len: 140 + }, + sponsoredBy: { + required: true, + len: 40 + } + } + }, + transactionId: 'f76f6dfd-d64f-4645-a29f-682bac7f431a', + bidId: '2f6ac468a9c15e', + adUnitCode: 'adunit-code', + sizes: [[1, 1]], + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + }, + }, + }; + + const serverResponse = { + noAd: { + results: [], + }, + banner: { + bids: { + ads: { + ad: '', + cpm: 1.22, + w: 300, + h: 250, + creativeId: 'xuidx62944aab4fx37f', + ttl: 60, + bidId: '2f6ac468a9c15e' + } + } + }, + native: { + bids: { + ads: { + cpm: 1.22, + title: 'title', + desc: 'description', + sponsoredBy: 'sponsoredBy', + image: 'https://ads-tr.bigmining.com/img/300_250_1.jpg', + w: 300, + h: 250, + ttl: 60, + bidId: '2f6ac468a9c15e', + creativeId: 'xuidx62944aab4fx37f', + isNative: 1, + impressionTrackers: ['https://ads-tr.bigmining.com/ad/view/beacon.gif'], + clickTrackers: ['https://ads-tr.bigmining.com/ad/view/beacon.png'], + clickUrl: 'http://www.garage.co.jp/ja/' + }, + } + } + }; + + const bidResponses = { + banner: { + requestId: '2f6ac468a9c15e', + cpm: 1.22, + width: 300, + height: 250, + creativeId: 'xuidx62944aab4fx37f', + currency: 'JPY', + netRevenue: true, + ttl: 60, + referrer: utils.getTopWindowUrl(), + ad: '', + }, + native: { + requestId: '2f6ac468a9c15e', + cpm: 1.22, + creativeId: 'xuidx62944aab4fx37f', + currency: 'JPY', + netRevenue: true, + ttl: 60, + native: { + image: { + url: 'https://ads-tr.bigmining.com/img/300_250_1.jpg', + width: 300, + height: 250 + }, + title: 'title', + body: 'description', + sponsoredBy: 'sponsoredBy', + clickUrl: 'http://www.garage.co.jp/ja/', + impressionTrackers: ['https://ads-tr.bigmining.com/ad/view/beacon.gif'], + clickTrackers: ['https://ads-tr.bigmining.com/ad/view/beacon.png'] + }, + referrer: utils.getTopWindowUrl(), + creativeid: 'xuidx62944aab4fx37f', + mediaType: NATIVE + } + }; + + it('no bid responses', () => { + const result = spec.interpretResponse({body: serverResponse.noAd}, bidRequests.banner); + expect(result.length).to.equal(0); + }); + it('handles banner responses', () => { + const result = spec.interpretResponse({body: serverResponse.banner}, bidRequests.banner)[0]; + expect(result.requestId).to.equal(bidResponses.banner.requestId); + expect(result.width).to.equal(bidResponses.banner.width); + expect(result.height).to.equal(bidResponses.banner.height); + expect(result.creativeId).to.equal(bidResponses.banner.creativeId); + 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); + }); + + it('handles native responses', () => { + const result = spec.interpretResponse({body: serverResponse.native}, bidRequests.native)[0]; + expect(result.requestId).to.equal(bidResponses.native.requestId); + expect(result.creativeId).to.equal(bidResponses.native.creativeId); + 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.body).to.equal(bidResponses.native.native.body); + expect(result.native.sponsoredBy).to.equal(bidResponses.native.native.sponsoredBy); + expect(result.native.image.url).to.equal(bidResponses.native.native.image.url); + expect(result.native.image.width).to.equal(bidResponses.native.native.image.width); + expect(result.native.image.height).to.equal(bidResponses.native.native.image.height); + 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/districtmDMXBidAdapter_spec.js b/test/spec/modules/districtmDMXBidAdapter_spec.js deleted file mode 100644 index 6fc83d88e6d..00000000000 --- a/test/spec/modules/districtmDMXBidAdapter_spec.js +++ /dev/null @@ -1,245 +0,0 @@ -/** - * Created by stevealliance on 2016-11-15. - */ - -import {expect} from 'chai'; -import {should} from 'chai'; -import Adaptor from '../../../modules/districtmDMXBidAdapter'; - -import adLoader from '../../../src/adloader'; - -var _each = function(obj, fn) { - for (var o in obj) { - fn(o, obj[o]); - } -} - -let districtm; -const PREBID_RESPONSE = function() { - return { - result: { - cpm: '3.45', - callbackId: '1490bd6bdc59ce', - width: 300, - height: 250, - banner: 'html' - }, - callback_uid: '1490bd6bdc59ce' - }; -} -const PREBID_PARAMS = { - bidderCode: 'districtmDMX', - requestId: '5ccedbd5-86c1-436f-8649-964262461eac', - bidderRequestId: '1490bd6bdc59ce', - start: new Date().getTime(), - bids: [{ - bidder: 'districtmDMX', - bidId: '84ab500420319d', - bidderRequestId: '1490bd6bdc59ce', - requestId: '5ccedbd5-86c1-436f-8649-964262461eac', - placementCode: 'golden', - params: { - placement: 109801, - floor: '1.00' - }, - sizes: [[300, 250], [300, 600]] - }] -}; - -function resetDm() { - window.hb_dmx_res = undefined; -} - -function activated() { - window.hb_dmx_res = { - ssp: {}, - bh() { - - }, - auction: { - fixSize(s) { - let size; - if (!Array.isArray(s[0])) { - size = [s[0] + 'x' + s[1]]; - } else { - size = s.map(ss => { - return ss[0] + 'x' + ss[1]; - }) - } - - return size; - }, - - run(a, b, c) { - - } - } - } -} - -function definitions() { - districtm.callBids({ - bidderCode: 'districtmDMX', - bids: [ - { - bidder: 'districtmDMX', - adUnitCode: 'golden', - sizes: [[728, 90]], - params: { - siteId: '101000' - } - }, - { - bidder: 'districtmDMX', - adUnitCode: 'stevealliance', - sizes: [[300, 250]], - params: { - siteId: '101000' - } - } - ] - }); -} -describe('DistrictM adapter test', () => { - describe('File loading', () => { - let districtm; - afterEach(() => { - districtm = new Adaptor(); - adLoader.loadScript(districtm.districtUrl, function() {}); - }) - - it('For loading file ', () => { - expect(!window.hb_dmx_res).to.equal(true); - }) - }) - - describe('check for library do exists', () => { - it('library was not loaded', () => { - expect(!window.hb_dmx_res).to.equal(true); - }) - - it('library is now available', () => { - activated(); - - expect(!!window.hb_dmx_res).to.equal(true); - }) - }) - - describe('Check if size get clean', () => { - beforeEach(() => { - activated(); - }) - it('size clean up using fixe size', () => { - activated(); - - expect(window.hb_dmx_res.auction.fixSize([728, 90])[0]).to.equal(['728x90'][0]); - expect(window.hb_dmx_res.auction.fixSize([[300, 250], [300, 600]]).toString()).to.equal(['300x250', '300x600'].toString()); - }) - }) - - describe('Check call bids return no errors', () => { - let districtm; - beforeEach(() => { - districtm = new Adaptor(); - }); - it('check value push using cal bids', () => { - let obj = districtm.callBids(PREBID_PARAMS); - obj.should.have.property('bidderCode'); - obj.should.have.property('requestId'); - obj.should.have.property('bidderRequestId'); - obj.should.have.property('start'); - obj.should.have.property('bids'); - }) - it('check if value got pass correctly for DM params', () => { - let dm = districtm.callBids(PREBID_PARAMS).bids.map(bid => bid); - dm.forEach(a => { - a.should.have.property('bidder'); - a.should.have.property('requestId'); - a.should.have.property('bidderRequestId'); - a.should.have.property('placementCode'); - a.should.have.property('params'); - a.should.have.property('sizes'); - expect(a.bidder).to.equal('districtmDMX'); - expect(a.placementCode).to.equal('golden'); - expect(a.params.placement).to.equal(109801); - }) - }) - }) - - describe('Run prebid definitions !', () => { - let districtm; - beforeEach(() => { - districtm = new Adaptor(); - }) - - it('Run and return send bids', () => { - let sendBids = districtm.sendBids(PREBID_PARAMS); - sendBids.forEach(sb => { - expect(sb.sizes.toString()).to.equal([[300, 250], [300, 600]].toString()); - }) - }) - }) - - describe('HandlerRes function test', () => { - let districtm; - - beforeEach(() => { - districtm = new Adaptor(); - }) - - it('it\'s now time to play with the response ...', () => { - let result = districtm.handlerRes(PREBID_RESPONSE(), PREBID_PARAMS); - _each(result, function(k, v) { - - }) - - expect(result.cpm).to.equal('3.45'); - expect(result.width).to.equal(300); - expect(result.height).to.equal(250); - expect(result.ad).to.equal('html'); - }) - it('it\'s now time to play with the response failure...', () => { - let result = districtm.handlerRes({result: {cpm: 0}}, PREBID_PARAMS); - - result.should.have.property('bidderCode'); - }) - }) - - describe('look at the adloader', () => { - let districtm; - beforeEach(() => { - districtm = new Adaptor(); - sinon.stub(adLoader, 'loadScript'); - }) - - it('Verify districtm library is downloaded if nessesary', () => { - resetDm(); - districtm.callBids(PREBID_PARAMS); - let libraryLoadCall = adLoader.loadScript.firstCall.args[0]; - let callback = adLoader.loadScript.firstCall.args[1]; - expect(libraryLoadCall).to.equal('http://prebid.districtm.ca/lib.js'); - expect(callback).to.be.a('function'); - }); - - afterEach(() => { - adLoader.loadScript.restore(); - }) - }); - describe('run send bid from within !!!', () => { - beforeEach(() => { - districtm = new Adaptor(); - sinon.stub(districtm, 'sendBids'); - }) - - it('last test on send bids', () => { - resetDm(); - districtm.sendBids(PREBID_PARAMS); - expect(districtm.sendBids.calledOnce).to.be.true; - expect(districtm.sendBids.firstCall.args[0]).to.be.a('object'); - }); - - afterEach(() => { - districtm.sendBids.restore(); - }) - }); -}); diff --git a/test/spec/modules/ebdrBidAdapter_spec.js b/test/spec/modules/ebdrBidAdapter_spec.js new file mode 100644 index 00000000000..3ec5a4f0a81 --- /dev/null +++ b/test/spec/modules/ebdrBidAdapter_spec.js @@ -0,0 +1,235 @@ +import { expect } from 'chai'; +import { spec } from 'modules/ebdrBidAdapter'; +import { VIDEO, BANNER } from 'src/mediaTypes'; +import * as utils from 'src/utils'; + +describe('ebdrBidAdapter', () => { + let bidRequests; + + beforeEach(() => { + bidRequests = [ + { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bidder: 'ebdr', + params: { + zoneid: '99999', + bidfloor: '1.00', + IDFA: 'xxx-xxx', + ADID: 'xxx-xxx', + latitude: '34.089811', + longitude: '-118.392805' + }, + bidId: '2c5e8a1a84522d', + bidderRequestId: '1d0c4017f02458', + auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f314' + }, { + adUnitCode: 'div-gpt-ad-1460505748561-1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [300, 250] + } + }, + bidder: 'ebdr', + params: { + zoneid: '99998', + bidfloor: '1.00', + IDFA: 'xxx-xxx', + ADID: 'xxx-xxx', + latitude: '34.089811', + longitude: '-118.392805' + }, + bidId: '23a01e95856577', + bidderRequestId: '1d0c4017f02458', + auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f314' + } + ]; + }); + + describe('spec.isBidRequestValid', () => { + it('should return true when the required params are passed', () => { + const bidRequest = bidRequests[0]; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when the only required param is missing', () => { + const bidRequest = bidRequests[0]; + bidRequest.params = { + zoneid: '99998', + bidfloor: '1.00', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when the "bidfloor" param is missing', () => { + const bidRequest = bidRequests[0]; + bidRequest.params = { + zoneid: '99998', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false when no bid params are passed', () => { + const bidRequest = bidRequests[0]; + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when a bid request is not passed', () => { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).to.equal(false); + }); + }); + + describe('spec.buildRequests', () => { + describe('for banner bids', () => { + it('must handle an empty bid size', () => { + bidRequests[0].mediaTypes = { banner: {} }; + const requests = spec.buildRequests(bidRequests); + const bidRequest = {}; + bidRequest['2c5e8a1a84522d'] = { mediaTypes: BANNER, w: null, h: null }; + expect(requests.bids['2c5e8a1a84522d']).to.deep.equals(bidRequest['2c5e8a1a84522d']); + }); + it('should create a single GET', () => { + bidRequests[0].mediaTypes = { banner: {} }; + bidRequests[1].mediaTypes = { banner: {} }; + const requests = spec.buildRequests(bidRequests); + expect(requests.method).to.equal('GET'); + }); + it('must parse bid size from a nested array', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {sizes: [[ width, height ]]} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = {}; + data['2c5e8a1a84522d'] = { mediaTypes: BANNER, w: width, h: height }; + expect(requests.bids['2c5e8a1a84522d']).to.deep.equal(data['2c5e8a1a84522d']); + }); + }); + describe('for video bids', () => { + it('must handle an empty bid size', () => { + bidRequests[1].mediaTypes = { video: {} }; + const requests = spec.buildRequests(bidRequests); + const bidRequest = {}; + bidRequest['23a01e95856577'] = { mediaTypes: VIDEO, w: null, h: null }; + expect(requests.bids['23a01e95856577']).to.deep.equals(bidRequest['23a01e95856577']); + }); + + it('should create a GET request for each bid', () => { + const bidRequest = bidRequests[1]; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests.method).to.equal('GET'); + }); + }); + }); + + describe('spec.interpretResponse', () => { + describe('for video bids', () => { + it('should return no bids if the response is not valid', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid video bid response', () => { + const ebdrReq = {bids: {}}; + bidRequests.forEach(bid => { + let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); + ebdrReq.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: '1d0c4017f02458', seatbid: [{bid: [{id: '23a01e95856577', impid: '23a01e95856577', price: 0.81, adid: 'abcde-12345', nurl: 'https://cdn0.bnmla.com/vtest.xml', adm: '\nStatic VASTStatic VAST Tag00:00:15http://www.engagebdr.com/c', adomain: ['advertiserdomain.com'], iurl: '', cid: 'campaign1', crid: 'abcde-12345', w: 300, h: 250}], seat: '19513bcfca8006'}], bidid: '19513bcfca8006', cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, ebdrReq); + expect(bidResponse[0]).to.deep.equal({ + requestId: bidRequests[1].bidId, + vastXml: serverResponse.seatbid[0].bid[0].adm, + mediaType: 'video', + 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, + currency: 'USD', + netRevenue: true, + ttl: 3600, + vastUrl: serverResponse.seatbid[0].bid[0].nurl + }); + }); + }); + + describe('for banner bids', () => { + it('should return no bids if the response is not valid', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response is empty', () => { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: [] }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return valid banner bid responses', () => { + const ebdrReq = {bids: {}}; + bidRequests.forEach(bid => { + let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); + ebdrReq.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: '1d0c4017f02458', 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 }, ebdrReq); + expect(bidResponse[0]).to.deep.equal({ + requestId: bidRequests[ 0 ].bidId, + ad: serverResponse.seatbid[0].bid[0].adm, + mediaType: 'banner', + 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, + currency: 'USD', + netRevenue: true, + ttl: 3600 + }); + }); + }); + }); + describe('spec.getUserSyncs', () => { + let syncOptions + beforeEach(() => { + syncOptions = { + enabledBidders: ['ebdr'], // only these bidders are allowed to sync + pixelEnabled: true + } + }); + it('sucess with usersync url', () => { + const serverResponse = {id: '1d0c4017f02458', seatbid: [{bid: [{id: '2c5e8a1a84522d', impid: '2c5e8a1a84522d', price: 0.81, adid: 'abcde-12345', nurl: '', adm: '
', adomain: ['advertiserdomain.com'], iurl: '//match.bnmla.com/usersync?sspid=59&redir=', cid: 'campaign1', crid: 'abcde-12345', w: 300, h: 250}], seat: '19513bcfca8006'}], bidid: '19513bcfca8006', cur: 'USD', w: 300, h: 250}; + const result = []; + result.push({type: 'image', url: '//match.bnmla.com/usersync?sspid=59&redir='}); + expect(spec.getUserSyncs(syncOptions, { body: serverResponse })).to.deep.equal(result); + }); + + it('sucess without usersync url', () => { + const serverResponse = {id: '1d0c4017f02458', 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 result = []; + expect(spec.getUserSyncs(syncOptions, { body: serverResponse })).to.deep.equal(result); + }); + it('empty response', () => { + const serverResponse = {}; + const result = []; + expect(spec.getUserSyncs(syncOptions, { body: serverResponse })).to.deep.equal(result); + }); + }); +}); diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..cd538815954 --- /dev/null +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -0,0 +1,166 @@ +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 events = require('src/events'); +let constants = require('src/constants.json'); + +describe('eplanning analytics adapter', () => { + let xhr; + let requests; + + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => { requests.push(request) }; + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(() => { + xhr.restore(); + events.getEvents.restore(); + eplAnalyticsAdapter.disableAnalytics(); + }); + + describe('track', () => { + it('builds and sends auction data', () => { + sinon.spy(eplAnalyticsAdapter, 'track'); + + let auctionTimestamp = 1496510254313; + let pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + let initOptions = { + host: 'https://ads.ar.e-planning.net/hba/1/', + ci: '12345' + }; + let pbidderCode = 'adapter'; + + const bidRequest = { + bidderCode: pbidderCode, + auctionId: pauctionId, + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: pbidderCode, + placementCode: 'container-1', + bidId: '208750227436c1', + bidderRequestId: '1a6fc81528d0f6', + auctionId: pauctionId, + startTime: 1509369418389, + sizes: [[300, 250]], + }], + auctionStart: 1509369418387, + timeout: 3000, + start: 1509369418389 + }; + + const bidResponse = { + bidderCode: pbidderCode, + adId: '208750227436c1', + cpm: 0.015, + auctionId: pauctionId, + responseTimestamp: 1509369418832, + requestTimestamp: 1509369418389, + bidder: pbidderCode, + timeToRespond: 443, + size: '300x250', + width: 300, + height: 250, + }; + + let bidTimeout = [ + { + bidId: '208750227436c1', + bidder: pbidderCode, + auctionId: pauctionId + } + ]; + + adaptermanager.registerAnalyticsAdapter({ + code: 'eplanning', + adapter: eplAnalyticsAdapter + }); + + adaptermanager.enableAnalytics({ + provider: 'eplanning', + options: initOptions + }); + + // Emit the events with the "real" arguments + + // Step 1: Send auction init event + events.emit(constants.EVENTS.AUCTION_INIT, { + auctionId: pauctionId, + timestamp: auctionTimestamp + }); + + // Step 2: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + + // Step 3: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + // Step 4: Send bid time out event + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + + // Step 5: Send auction bid won event + events.emit(constants.EVENTS.BID_WON, { + adId: 'adIdData', + ad: 'adContent', + auctionId: pauctionId, + width: 300, + height: 250 + }); + + // Step 6: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, {auctionId: pauctionId}); + + // Step 7: Find the request data sent (filtering other hosts) + requests = requests.filter(req => { + return req.url.indexOf(initOptions.host) > -1; + }); + expect(requests.length).to.equal(1); + + expect(includes([initOptions.host + initOptions.ci], requests[0].url)); + expect(includes(['https://ads.ar.e-planning.net/hba/1/12345?d='], requests[0].url)); + + let info = requests[0].url; + let purl = parseURL(info); + let eplData = JSON.parse(decodeURIComponent(purl.search.d)); + + // Step 8 check that 6 events were sent + expect(eplData.length).to.equal(6); + + // Step 9 verify that we only receive the parameters we need + let expectedEventValues = [ + // AUCTION INIT + {ec: constants.EVENTS.AUCTION_INIT, + p: {auctionId: pauctionId, time: auctionTimestamp}}, + // BID REQ + {ec: constants.EVENTS.BID_REQUESTED, + p: {auctionId: pauctionId, time: 1509369418389, bidder: pbidderCode, bids: [{time: 1509369418389, sizes: [[300, 250]], bidder: pbidderCode, placementCode: 'container-1', auctionId: pauctionId}]}}, + // BID RESP + {ec: constants.EVENTS.BID_RESPONSE, + p: {auctionId: pauctionId, bidder: pbidderCode, cpm: 0.015, size: '300x250', time: 1509369418832}}, + // BID T.O. + {ec: constants.EVENTS.BID_TIMEOUT, + p: [{auctionId: pauctionId, bidder: pbidderCode}]}, + // BID WON + {ec: constants.EVENTS.BID_WON, + p: {auctionId: pauctionId, size: '300x250'}}, + // AUCTION END + {ec: constants.EVENTS.AUCTION_END, + p: {auctionId: pauctionId}} + ]; + + for (let evid = 0; evid < eplData.length; evid++) { + expect(eplData[evid]).to.deep.equal(expectedEventValues[evid]); + } + + // Step 10 check that the host to send the ajax request is configurable via options + expect(eplAnalyticsAdapter.context.host).to.equal(initOptions.host); + + // Step 11 verify that we received 6 events + sinon.assert.callCount(eplAnalyticsAdapter.track, 6); + }); + }); +}); diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index bf09f42f6e7..68b9e1b263f 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -1,112 +1,350 @@ -describe('eplanning adapter tests', function () { - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - var adapter = require('modules/eplanningBidAdapter'); - var adLoader = require('src/adloader'); - var expect = require('chai').expect; - var bidmanager = require('src/bidmanager'); - var CONSTANTS = require('src/constants.json'); - - var DEFAULT_PARAMS = { - bidderCode: 'eplanning', - bids: [{ - code: 'div-gpt-ad-1460505748561-0', - sizes: [[300, 250], [300, 200]], - bidder: 'eplanning', - params: { - ci: '18f66' - } - }] - }; +import { expect } from 'chai'; +import { spec } from 'modules/eplanningBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; - var PARAMS_SERVER_TEST = { - bidderCode: 'eplanning', - bids: [{ - code: 'div-gpt-ad-1460505748561-0', - sizes: [[300, 250], [300, 600]], - bidder: 'eplanning', - params: { - ci: '18f66', - t: '1' - } - }] +describe('E-Planning Adapter', () => { + const adapter = newBidder('spec'); + const CI = '12345'; + const ADUNIT_CODE = 'adunit-code'; + const ADUNIT_CODE2 = 'adunit-code-dos'; + const CLEAN_ADUNIT_CODE2 = 'adunitcodedos'; + const CLEAN_ADUNIT_CODE = 'adunitcode'; + const BID_ID = '123456789'; + const BID_ID2 = '987654321'; + const CPM = 1.3; + const W = '300'; + const H = '250'; + const ADM = '
This is an ad
'; + const I_ID = '7854abc56248f873'; + const CRID = '1234567890'; + const TEST_ISV = 'leles.e-planning.net'; + const validBid = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[300, 250], [300, 600]], }; - - var RESPONSE_AD = { - bids: [{ - placementCode: 'div-gpt-ad-1460505748561-0', - ad: { - ad: '

test ad

', - cpm: 1, - width: 300, - height: 250 - } - }] + const validBid2 = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], }; - - var RESPONSE_EMPTY = { - bids: [{ - placementCode: 'div-gpt-ad-1460505748561-0' - }] + const testBid = { + 'bidder': 'eplanning', + 'params': { + 't': 1, + 'isv': TEST_ISV + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[300, 250], [300, 600]], + }; + const invalidBid = { + 'bidder': 'eplanning', + 'params': { + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + }; + const response = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': CLEAN_ADUNIT_CODE, + 'a': [{ + 'adm': ADM, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM + }], + }], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + const responseWithTwoAdunits = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': CLEAN_ADUNIT_CODE, + 'a': [{ + 'adm': ADM, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM + }] + }, { + 'k': CLEAN_ADUNIT_CODE2, + 'a': [{ + 'adm': ADM, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM + }], + }, + ], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } }; + const responseWithNoAd = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': 'spname', + }], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + const responseWithNoSpace = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + + 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 bid has ci parameter', () => { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when bid does not have ci parameter and is not a test bid', () => { + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return true when bid does not have ci parameter but is a test bid', () => { + expect(spec.isBidRequestValid(testBid)).to.equal(true); + }); + }); - var stubAddBidResponse; + describe('buildRequests', () => { + let bidRequests = [validBid]; - describe('eplanning tests', function() { - beforeEach(function() { - stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + it('should create the url correctly', () => { + const url = spec.buildRequests(bidRequests).url; + expect(url).to.equal('//ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); }); - afterEach(function() { - stubAddBidResponse.restore(); + + it('should return GET method', () => { + const method = spec.buildRequests(bidRequests).method; + expect(method).to.equal('GET'); + }); + + it('should return r parameter with value pbjs', () => { + const r = spec.buildRequests(bidRequests).data.r; + expect(r).to.equal('pbjs'); }); - it('callback function should exist', function() { - expect($$PREBID_GLOBAL$$.processEPlanningResponse).to.exist.and.to.be.a('function'); + it('should return pbv parameter with value prebid version', () => { + const pbv = spec.buildRequests(bidRequests).data.pbv; + expect(pbv).to.equal('$prebid.version$'); }); - it('creates a bid response if bid exists', function() { - adapter().callBids(DEFAULT_PARAMS); - $$PREBID_GLOBAL$$.processEPlanningResponse(RESPONSE_AD); + it('should return e parameter with value according to the adunit sizes', () => { + const e = spec.buildRequests(bidRequests).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE + ':300x250,300x600'); + }); - var bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; - var bidObject = stubAddBidResponse.getCall(0).args[1]; + it('should return correct e parameter with more than one adunit', () => { + const NEW_CODE = ADUNIT_CODE + '2'; + const CLEAN_NEW_CODE = CLEAN_ADUNIT_CODE + '2'; + const anotherBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + }, + 'adUnitCode': NEW_CODE, + 'sizes': [[100, 100]], + }; + bidRequests.push(anotherBid); - expect(bidPlacementCode).to.equal('div-gpt-ad-1460505748561-0'); - expect(bidObject.cpm).to.equal(1); - expect(bidObject.ad).to.equal('

test ad

'); - expect(bidObject.width).to.equal(300); - expect(bidObject.height).to.equal(250); - expect(bidObject.getStatusCode()).to.equal(1); - expect(bidObject.bidderCode).to.equal('eplanning'); + const e = spec.buildRequests(bidRequests).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE + ':300x250,300x600+' + CLEAN_NEW_CODE + ':100x100'); }); - it('creates an empty bid response if there is no bid', function() { - adapter().callBids(DEFAULT_PARAMS); - $$PREBID_GLOBAL$$.processEPlanningResponse(RESPONSE_EMPTY); + it('should return correct e parameter when the adunit has no size', () => { + const noSizeBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + }; - var bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; - var bidObject = stubAddBidResponse.getCall(0).args[1]; + const e = spec.buildRequests([noSizeBid]).data.e; + expect(e).to.equal(CLEAN_ADUNIT_CODE + ':1x1'); + }); - expect(bidPlacementCode).to.equal('div-gpt-ad-1460505748561-0'); - expect(bidObject.getStatusCode()).to.equal(2); - expect(bidObject.bidderCode).to.equal('eplanning'); + it('should return ur parameter with current window url', () => { + const ur = spec.buildRequests(bidRequests).data.ur; + expect(ur).to.equal(utils.getTopWindowUrl()); }); - it('creates a bid response and sync users register ad', function() { - adapter().callBids(DEFAULT_PARAMS); - window.hbpb.rH({ - 'sI': { 'k': '18f66' }, - 'sec': { 'k': 'ROS' }, - 'sp': [ { 'k': 'div-gpt-ad-1460505748561-0', 'a': [{ 'w': 300, 'h': 250, 'adm': '

test ad

', 'pr': 1 }] } ], - 'cs': [ - '//test.gif', - { 'j': true, u: '//test.js' }, - { 'ifr': true, u: '//test.html', data: { 'test': 1 } } - ] - }); - var bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; - var bidObject = stubAddBidResponse.getCall(0).args[1]; - expect(bidObject.getStatusCode()).to.equal(2); + it('should return fr parameter when there is a referrer', () => { + const referrer = 'thisisafakereferrer'; + const stubGetReferrer = sinon.stub(utils, 'getTopWindowReferrer'); + stubGetReferrer.returns(referrer); + const fr = spec.buildRequests(bidRequests).data.fr; + expect(fr).to.equal(referrer); + stubGetReferrer.restore() + }); + + it('should return the testing url when the request has the t parameter', () => { + const url = spec.buildRequests([testBid]).url; + const expectedUrl = '//' + TEST_ISV + '/layers/t_pbjs_2.json'; + expect(url).to.equal(expectedUrl); + }); + + it('should return the parameter ncb with value 1', () => { + const ncb = spec.buildRequests(bidRequests).data.ncb; + expect(ncb).to.equal('1'); + }); + }); + + describe('interpretResponse', () => { + it('should return an empty array when there is no ads in the response', () => { + const bidResponses = spec.interpretResponse(responseWithNoAd); + expect(bidResponses).to.be.empty; + }); + + it('should return an empty array when there is no spaces in the response', () => { + const bidResponses = spec.interpretResponse(responseWithNoSpace); + expect(bidResponses).to.be.empty; + }); + + it('should correctly map the parameters in the response', () => { + const bidResponse = spec.interpretResponse(response, { adUnitToBidId: { [CLEAN_ADUNIT_CODE]: BID_ID } })[0]; + const expectedResponse = { + requestId: BID_ID, + cpm: CPM, + width: W, + height: H, + ad: ADM, + ttl: 120, + creativeId: CRID, + netRevenue: true, + currency: 'USD', + }; + expect(bidResponse).to.deep.equal(expectedResponse); + }); + }); + + describe('getUserSyncs', () => { + const sOptionsAllEnabled = { + pixelEnabled: true, + iframeEnabled: true + }; + const sOptionsAllDisabled = { + pixelEnabled: false, + iframeEnabled: false + }; + const sOptionsOnlyPixel = { + pixelEnabled: true, + iframeEnabled: false + }; + const sOptionsOnlyIframe = { + pixelEnabled: false, + iframeEnabled: true + }; + + it('should return an empty array if the response has no syncs', () => { + const noSyncsResponse = { cs: [] }; + const syncs = spec.getUserSyncs(sOptionsAllEnabled, [noSyncsResponse]); + expect(syncs).to.be.empty; + }); + + it('should return an empty array if there is no sync options enabled', () => { + const syncs = spec.getUserSyncs(sOptionsAllDisabled, [response]); + expect(syncs).to.be.empty; + }); + + it('should only return pixels if iframe is not enabled', () => { + const syncs = spec.getUserSyncs(sOptionsOnlyPixel, [response]); + syncs.forEach(sync => expect(sync.type).to.equal('image')); + }); + + it('should only return iframes if pixel is not enabled', () => { + const syncs = spec.getUserSyncs(sOptionsOnlyIframe, [response]); + syncs.forEach(sync => expect(sync.type).to.equal('iframe')); + }); + }); + + describe('adUnits mapping to bidId', () => { + it('should correctly map the bidId to the adunit', () => { + const requests = spec.buildRequests([validBid, validBid2]); + const responses = spec.interpretResponse(responseWithTwoAdunits, requests); + expect(responses[0].requestId).to.equal(BID_ID); + expect(responses[1].requestId).to.equal(BID_ID2); }); }); }); diff --git a/test/spec/modules/essensBidAdapter_spec.js b/test/spec/modules/essensBidAdapter_spec.js deleted file mode 100644 index aad3d15b0a9..00000000000 --- a/test/spec/modules/essensBidAdapter_spec.js +++ /dev/null @@ -1,828 +0,0 @@ -import { expect } from 'chai' -import Adapter from 'modules/essensBidAdapter' -import bidmanager from 'src/bidmanager' -import adLoader from 'src/adloader' -describe('Essens adapter tests', function () { - describe('Test callbid method ', function () { - let stubLoadScript - beforeEach(() => { - stubLoadScript = sinon.stub(adLoader, 'loadScript') - }) - - afterEach(() => - stubLoadScript.restore() - ) - - it('bid request without bid', () => { - const essensAdapter = new Adapter() - essensAdapter.callBids() - sinon.assert.notCalled(stubLoadScript) - }) - - it('bid request with missing parameter', () => { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1' - } - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - sinon.assert.notCalled(stubLoadScript) - }) - - it('Bid request with wrong parameter', () => { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'placement1-for_essensT1', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - randomParam: 'placement1' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1', - bidderRequestId: 'impression-for-essens-1', - } - ] - } - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - sinon.assert.notCalled(stubLoadScript) - }) - - it('add one valid requests', function () { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'placement1-for_essensT1', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement1' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1', - bidderRequestId: 'impression-for-essens-1', - } - ] - } - - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - - const url = stubLoadScript.getCall(0).args[0] - const payload = decodeURIComponent(url.split('&bid=')[1]) - const payloadJson = JSON.parse(payload) - - expect(payloadJson.ua).to.exist.and.to.be.a('string') - expect(payloadJson.url).to.exist.and.to.be.a('string') - expect(Object.keys(payloadJson.imp).length).to.equal(1) - expect(payloadJson.imp[0].impressionId).to.equal('placement1-for_essensT1') - expect(payloadJson.imp[0].placementId).to.equal('placement1') - expect(Object.keys(payloadJson.imp[0].sizes).length).to.equal(2) - expect(payloadJson.imp[0].sizes[0]).to.equal('100x110') - expect(payloadJson.imp[0].sizes[1]).to.equal('200x210') - sinon.assert.calledOnce(stubLoadScript) - }) - it('add more than one valid requests', function () { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'placement1-for_essensT2', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement1' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1', - bidderRequestId: 'impression-for-essens-1', - }, - { - bidId: 'placement2-for_essensT2', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement2' - }, - sizes: [ - [300, 310], - [400, 410] - ], - placementCode: 'div-media1-side_banner-1', - bidderRequestId: 'impression-for-essens-1', - }, - { - bidId: 'placement3-for_essensT2', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement3' - }, - sizes: [ - [500, 510], - [600, 610] - ], - placementCode: 'div-media1-side_banner-2', - bidderRequestId: 'impression-for-essens-1', - }, - ] - } - - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - - const url = stubLoadScript.getCall(0).args[0] - const payload = decodeURIComponent(url.split('&bid=')[1]) - const payloadJson = JSON.parse(payload) - - expect(payloadJson.ua).to.exist.and.to.be.a('string') - expect(payloadJson.url).to.exist.and.to.be.a('string') - expect(Object.keys(payloadJson.imp).length).to.equal(3) - expect(payloadJson.imp[0].impressionId).to.equal('placement1-for_essensT2') - expect(payloadJson.imp[0].placementId).to.equal('placement1') - expect(Object.keys(payloadJson.imp[0].sizes).length).to.equal(2) - expect(payloadJson.imp[0].sizes[0]).to.equal('100x110') - expect(payloadJson.imp[0].sizes[1]).to.equal('200x210') - - expect(payloadJson.imp[1].impressionId).to.equal('placement2-for_essensT2') - expect(payloadJson.imp[1].placementId).to.equal('placement2') - expect(Object.keys(payloadJson.imp[1].sizes).length).to.equal(2) - expect(payloadJson.imp[1].sizes[0]).to.equal('300x310') - expect(payloadJson.imp[1].sizes[1]).to.equal('400x410') - - expect(payloadJson.imp[2].impressionId).to.equal('placement3-for_essensT2') - expect(payloadJson.imp[2].placementId).to.equal('placement3') - expect(Object.keys(payloadJson.imp[2].sizes).length).to.equal(2) - expect(payloadJson.imp[2].sizes[0]).to.equal('500x510') - expect(payloadJson.imp[2].sizes[1]).to.equal('600x610') - sinon.assert.calledOnce(stubLoadScript) - }) - it('should fill all parameters', function () { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'placement1-for_essensT3', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement1', - dealId: '1234', - floorPrice: '23.478' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1', - bidderRequestId: 'impression-for-essens-1', - } - ] - } - - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - - const url = stubLoadScript.getCall(0).args[0] - const payload = decodeURIComponent(url.split('&bid=')[1]) - const payloadJson = JSON.parse(payload) - - expect(payloadJson.ua).to.exist.and.to.be.a('string') - expect(payloadJson.url).to.exist.and.to.be.a('string') - expect(Object.keys(payloadJson.imp).length).to.equal(1) - expect(payloadJson.imp[0].impressionId).to.equal('placement1-for_essensT3') - expect(payloadJson.imp[0].placementId).to.equal('placement1') - expect(Object.keys(payloadJson.imp[0].sizes).length).to.equal(2) - expect(payloadJson.imp[0].sizes[0]).to.equal('100x110') - expect(payloadJson.imp[0].sizes[1]).to.equal('200x210') - expect(payloadJson.imp[0].deal).to.equal('1234') - expect(payloadJson.imp[0].floorPrice).to.equal('23.478') - }) - it('invalid request: missing mandatory parameters', function () { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'placement1-for_essensT4', - bidder: 'essens', - requestId: 'essens-impression-1', - params: {}, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1', - bidderRequestId: 'impression-for-essens-1', - } - ] - } - - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - - sinon.assert.notCalled(stubLoadScript) - }) - }) - - describe('Test essensResponseHandler method', function () { - let stubAddBidResponse - beforeEach(() => { - stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse') - }) - - afterEach(() => { - stubAddBidResponse.restore() - }) - - it('Check method exist', function () { - expect($$PREBID_GLOBAL$$.essensResponseHandler).to.exist.and.to.be.a('function') - }) - - it('Check invalid response', function () { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'placement1-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement1' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1T1', - bidderRequestId: 'impression-for-essens-1', - } - ] - } - - const response = { - 'id': '1234' - } - - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - - $$PREBID_GLOBAL$$.essensResponseHandler(response) - - const bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0] - const bidObject1 = stubAddBidResponse.getCall(0).args[1] - - expect(bidPlacementCode1).to.equal('div-media1-top_banner-1T1') - expect(bidObject1.getStatusCode()).to.equal(2) - expect(bidObject1.bidderCode).to.equal('essens') - - sinon.assert.calledOnce(stubAddBidResponse) - }) - - it('Check empty response', function () { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'placement1-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement1' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1T2', - bidderRequestId: 'impression-for-essens-1', - }, - { - bidId: 'placement2-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement2' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-side_banner-1T2', - bidderRequestId: 'impression-for-essens-1', - }, - { - bidId: 'placement3-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement3' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-side_banner-2T2', - bidderRequestId: 'impression-for-essens-1', - }, - ] - } - - const response = { - 'id': '1234', - 'seatbid': [] - } - - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - - $$PREBID_GLOBAL$$.essensResponseHandler(response) - - const bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0] - const bidObject1 = stubAddBidResponse.getCall(0).args[1] - const bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0] - const bidObject2 = stubAddBidResponse.getCall(1).args[1] - const bidPlacementCode3 = stubAddBidResponse.getCall(2).args[0] - const bidObject3 = stubAddBidResponse.getCall(2).args[1] - - expect(bidPlacementCode1).to.equal('div-media1-top_banner-1T2') - expect(bidObject1.getStatusCode()).to.equal(2) - expect(bidObject1.bidderCode).to.equal('essens') - - expect(bidPlacementCode2).to.equal('div-media1-side_banner-1T2') - expect(bidObject2.getStatusCode()).to.equal(2) - expect(bidObject2.bidderCode).to.equal('essens') - - expect(bidPlacementCode3).to.equal('div-media1-side_banner-2T2') - expect(bidObject3.getStatusCode()).to.equal(2) - expect(bidObject3.bidderCode).to.equal('essens') - - sinon.assert.calledThrice(stubAddBidResponse) - }) - - it('Check valid response but invalid bid ', function () { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'bid-on-placement1-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement1' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1T3', - bidderRequestId: 'impression-for-essens-1', - }, - { - bidId: 'bid-on-placement2-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement2' - }, - sizes: [ - [300, 310], - [400, 410] - ], - placementCode: 'div-media1-side_banner-1T3', - bidderRequestId: 'impression-for-essens-1', - } - ] - } - - const response = { - 'id': 'impression-for-essens-1', - 'cur': 'USD', - 'seatbid': [{ - 'bid': [{ - 'id': 'responseOnBid1', - 'impid': 'bid-on-placement1-for_essens', - 'price': 9.01, - 'crid': 'creativeId1', - 'dealid': 'dealId1', - 'h': 100, - 'w': 110 - // ,'ext': { - // 'adUrl': 'creative-link2' - // } - }, - { - 'id': 'responseOnBid1', - // 'impid': 'bid-on-placement2-for_essens', - 'price': 9.01, - 'crid': 'creativeId1', - 'dealid': 'dealId1', - 'h': 300, - 'w': 310, - 'ext': { - 'adUrl': 'creative-link2' - } - }] - }] - } - - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - - $$PREBID_GLOBAL$$.essensResponseHandler(response) - - let bidPlacementCode1 - let bidPlacementCode2 - let bidObject1 - let bidObject2 - - if (stubAddBidResponse.getCall(0).args[0] === 'div-media1-top_banner-1T3') { - bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0] - bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0] - bidObject1 = stubAddBidResponse.getCall(0).args[1] - bidObject2 = stubAddBidResponse.getCall(0).args[1] - } else { - bidPlacementCode1 = stubAddBidResponse.getCall(1).args[0] - bidPlacementCode2 = stubAddBidResponse.getCall(0).args[0] - bidObject1 = stubAddBidResponse.getCall(1).args[1] - bidObject2 = stubAddBidResponse.getCall(0).args[1] - } - - expect(bidPlacementCode1).to.equal('div-media1-top_banner-1T3') - expect(bidObject1.getStatusCode()).to.equal(2) - expect(bidObject1.bidderCode).to.equal('essens') - - expect(bidPlacementCode2).to.equal('div-media1-side_banner-1T3') - expect(bidObject2.getStatusCode()).to.equal(2) - expect(bidObject2.bidderCode).to.equal('essens') - - sinon.assert.calledTwice(stubAddBidResponse) - }) - - it('Check single non empty minimal valid response', function () { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'bid-on-placement1-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement1' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1T3', - bidderRequestId: 'impression-for-essens-1', - } - ] - } - - const response = { - 'id': 'impression-for-essens-1', - 'cur': 'USD', - 'seatbid': [{ - 'bid': [{ - 'id': 'responseOnBid1', - 'impid': 'bid-on-placement1-for_essens', - 'price': 9.01, - 'crid': 'creativeId1', - 'h': 300, - 'w': 310, - 'ext': { - 'adUrl': 'creative-link' - } - }] - }] - } - - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - - $$PREBID_GLOBAL$$.essensResponseHandler(response) - - const bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0] - const bidObject1 = stubAddBidResponse.getCall(0).args[1] - - expect(bidPlacementCode1).to.equal('div-media1-top_banner-1T3') - expect(bidObject1.getStatusCode()).to.equal(1) - expect(bidObject1.bidderCode).to.equal('essens') - expect(bidObject1.creative_id).to.equal('creativeId1') - expect(bidObject1.cpm).to.equal(9.01) - expect(bidObject1.height).to.equal(300) - expect(bidObject1.width).to.equal(310) - expect(bidObject1.adUrl).to.equal('creative-link') - expect(bidObject1.adId).to.equal('bid-on-placement1-for_essens') - - sinon.assert.calledOnce(stubAddBidResponse) - }) - - it('Check single non empty response', function () { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'bid-on-placement1-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement1' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1T3', - bidderRequestId: 'impression-for-essens-1', - } - ] - } - - const response = { - 'id': 'impression-for-essens-1', - 'cur': 'USD', - 'seatbid': [{ - 'bid': [{ - 'id': 'responseOnBid1', - 'impid': 'bid-on-placement1-for_essens', - 'price': 9.01, - 'crid': 'creativeId1', - 'dealid': 'dealId1', - 'h': 300, - 'w': 310, - 'ext': { - 'adUrl': 'creative-link' - } - }] - }] - } - - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - - $$PREBID_GLOBAL$$.essensResponseHandler(response) - - const bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0] - const bidObject1 = stubAddBidResponse.getCall(0).args[1] - - expect(bidPlacementCode1).to.equal('div-media1-top_banner-1T3') - expect(bidObject1.getStatusCode()).to.equal(1) - expect(bidObject1.bidderCode).to.equal('essens') - expect(bidObject1.creative_id).to.equal('creativeId1') - expect(bidObject1.cpm).to.equal(9.01) - expect(bidObject1.height).to.equal(300) - expect(bidObject1.width).to.equal(310) - expect(bidObject1.adUrl).to.equal('creative-link') - expect(bidObject1.adId).to.equal('bid-on-placement1-for_essens') - expect(bidObject1.dealId).to.equal('dealId1') - - sinon.assert.calledOnce(stubAddBidResponse) - }) - - it('Check multiple non empty response', function () { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'bid-on-placement1-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement1' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1T4', - bidderRequestId: 'impression-for-essens-1', - }, - { - bidId: 'bid-on-placement2-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement2' - }, - sizes: [ - [300, 310], - [400, 410] - ], - placementCode: 'div-media1-side_banner-1T4', - bidderRequestId: 'impression-for-essens-1', - } - ] - } - - const response = { - 'id': 'impression-for-essens-1', - 'cur': 'USD', - 'seatbid': [{ - 'bid': [{ - 'id': 'responseOnBid1', - 'impid': 'bid-on-placement1-for_essens', - 'price': 9.01, - 'crid': 'creativeId1', - 'dealid': 'dealId1', - 'h': 100, - 'w': 110, - 'ext': { - 'adUrl': 'creative-link1' - } - }, - { - 'id': 'responseOnBid2', - 'impid': 'bid-on-placement2-for_essens', - 'price': 9.02, - 'crid': 'creativeId2', - 'dealid': 'dealId2', - 'h': 400, - 'w': 410, - 'ext': { - 'adUrl': 'creative-link2' - } - }] - }] - } - - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - - $$PREBID_GLOBAL$$.essensResponseHandler(response) - - let bidPlacementCode1 - let bidPlacementCode2 - let bidObject1 - let bidObject2 - - if (stubAddBidResponse.getCall(0).args[0] === 'div-media1-top_banner-1T4') { - bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0] - bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0] - bidObject1 = stubAddBidResponse.getCall(0).args[1] - bidObject2 = stubAddBidResponse.getCall(1).args[1] - } else { - bidPlacementCode1 = stubAddBidResponse.getCall(1).args[0] - bidPlacementCode2 = stubAddBidResponse.getCall(0).args[0] - bidObject1 = stubAddBidResponse.getCall(1).args[1] - bidObject2 = stubAddBidResponse.getCall(0).args[1] - } - - expect(bidPlacementCode1).to.equal('div-media1-top_banner-1T4') - expect(bidObject1.getStatusCode()).to.equal(1) - expect(bidObject1.bidderCode).to.equal('essens') - expect(bidObject1.creative_id).to.equal('creativeId1') - expect(bidObject1.cpm).to.equal(9.01) - expect(bidObject1.height).to.equal(100) - expect(bidObject1.width).to.equal(110) - expect(bidObject1.adUrl).to.equal('creative-link1') - expect(bidObject1.adId).to.equal('bid-on-placement1-for_essens') - expect(bidObject1.dealId).to.equal('dealId1') - - expect(bidPlacementCode2).to.equal('div-media1-side_banner-1T4') - expect(bidObject2.getStatusCode()).to.equal(1) - expect(bidObject2.bidderCode).to.equal('essens') - expect(bidObject2.creative_id).to.equal('creativeId2') - expect(bidObject2.cpm).to.equal(9.02) - expect(bidObject2.height).to.equal(400) - expect(bidObject2.width).to.equal(410) - expect(bidObject2.adUrl).to.equal('creative-link2') - expect(bidObject2.adId).to.equal('bid-on-placement2-for_essens') - expect(bidObject2.dealId).to.equal('dealId2') - - sinon.assert.calledTwice(stubAddBidResponse) - }) - - it('Check empty and non empty mixed response', function () { - const bidderRequest = { - bidderCode: 'essens', - requestId: 'impression-1', - bidderRequestId: 'impression-for-essens-1', - bids: [ - { - bidId: 'bid-on-placement1-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement1' - }, - sizes: [ - [100, 110], - [200, 210] - ], - placementCode: 'div-media1-top_banner-1T5', - bidderRequestId: 'impression-for-essens-1', - }, - { - bidId: 'bid-on-placement2-for_essens', - bidder: 'essens', - requestId: 'essens-impression-1', - params: { - placementId: 'placement2' - }, - sizes: [ - [300, 310], - [400, 410] - ], - placementCode: 'div-media1-side_banner-1T5', - bidderRequestId: 'impression-for-essens-1', - } - ] - } - - const response = { - 'id': 'impression-for-essens-1', - 'cur': 'USD', - 'seatbid': [{ - 'bid': [{ - 'id': 'responseOnBid1', - 'impid': 'bid-on-placement2-for_essens', - 'price': 9.01, - 'crid': 'creativeId1', - 'dealid': 'dealId1', - 'h': 500, - 'w': 510, - 'ext': { - 'adUrl': 'creative-link' - } - }] - }] - } - - const essensAdapter = new Adapter() - essensAdapter.callBids(bidderRequest) - - $$PREBID_GLOBAL$$.essensResponseHandler(response) - - let bidPlacementCode1 - let bidPlacementCode2 - let bidObject1 - let bidObject2 - - if (stubAddBidResponse.getCall(0).args[0] === 'div-media1-side_banner-1T5') { - bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0] - bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0] - bidObject1 = stubAddBidResponse.getCall(0).args[1] - bidObject2 = stubAddBidResponse.getCall(1).args[1] - } else { - bidPlacementCode1 = stubAddBidResponse.getCall(1).args[0] - bidPlacementCode2 = stubAddBidResponse.getCall(0).args[0] - bidObject1 = stubAddBidResponse.getCall(1).args[1] - bidObject2 = stubAddBidResponse.getCall(0).args[1] - } - - expect(bidPlacementCode1).to.equal('div-media1-side_banner-1T5') - expect(bidObject1.getStatusCode()).to.equal(1) - expect(bidObject1.bidderCode).to.equal('essens') - expect(bidObject1.creative_id).to.equal('creativeId1') - expect(bidObject1.cpm).to.equal(9.01) - expect(bidObject1.height).to.equal(500) - expect(bidObject1.width).to.equal(510) - expect(bidObject1.adUrl).to.equal('creative-link') - expect(bidObject1.adId).to.equal('bid-on-placement2-for_essens') - expect(bidObject1.dealId).to.equal('dealId1') - - expect(bidPlacementCode2).to.equal('div-media1-top_banner-1T5') - expect(bidObject2.getStatusCode()).to.equal(2) - expect(bidObject2.bidderCode).to.equal('essens') - - sinon.assert.calledTwice(stubAddBidResponse) - }) - }) -}) diff --git a/test/spec/modules/fairtradeBidAdapter_spec.js b/test/spec/modules/fairtradeBidAdapter_spec.js new file mode 100644 index 00000000000..07c26e8f0c1 --- /dev/null +++ b/test/spec/modules/fairtradeBidAdapter_spec.js @@ -0,0 +1,289 @@ +import { expect } from 'chai'; +import { spec } from 'modules/fairtradeBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('FairTradeAdapter', function () { + 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', () => { + let bid = { + 'bidder': 'fairtrade', + 'params': { + 'uid': '166' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + 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', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '165' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '165' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '167' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', () => { + const request = spec.buildRequests([bidRequests[0]]); + 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', 'net'); + expect(payload).to.have.property('auids', '165'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('auids must not be duplicated', () => { + const request = spec.buildRequests(bidRequests); + 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', 'net'); + expect(payload).to.have.property('auids', '165,167'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', () => { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests); + 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', '165,167'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', () => { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests); + 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', 'net'); + expect(payload).to.have.property('auids', '165,167'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; + }); + }); + + describe('interpretResponse', () => { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 165, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 166, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 167, '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', () => { + const bidRequests = [ + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '165' + }, + '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': 165, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + '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', () => { + const bidRequests = [ + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '165' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '166' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '165' + }, + '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': 165, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 165, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 166, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
test content 2
', + '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', () => { + const bidRequests = [ + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '167' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '168' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'fairtrade', + 'params': { + 'uid': '169' + }, + '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/featureforwardBidAdapter_spec.js b/test/spec/modules/featureforwardBidAdapter_spec.js deleted file mode 100644 index 9c6b91d5a36..00000000000 --- a/test/spec/modules/featureforwardBidAdapter_spec.js +++ /dev/null @@ -1,87 +0,0 @@ -import {expect} from 'chai'; -import FeatureForwardAdapter from 'modules/featureforwardBidAdapter'; -import bidManager from 'src/bidmanager'; -import * as ajax from 'src/ajax'; -import {parse as parseURL} from 'src/url'; - -describe('FeatureForward Adapter Tests', () => { - let featureForwardAdapter = new FeatureForwardAdapter(); - let slotConfigs; - let ajaxStub; - beforeEach(() => { - sinon.stub(bidManager, 'addBidResponse'); - ajaxStub = sinon.stub(ajax, 'ajax'); - slotConfigs = { - bids: [ - { - sizes: [[300, 250]], - bidder: 'featureforward', - placementCode: 'test1_placement', - params: { - pubId: '001', - siteId: '111', - placementId: '1', - } - }] - }; - }); - - afterEach(() => { - bidManager.addBidResponse.restore(); - ajaxStub.restore(); - }); - - it('Verify requests sent to FeatureForward', () => { - featureForwardAdapter.callBids(slotConfigs); - var call = ajaxStub.firstCall.args[0]; - var request = JSON.parse(ajaxStub.args[0][2]); - var creds = ajaxStub.args[0][3]; - expect(call).to.equal('http://prmbdr.featureforward.com/newbidder/bidder1_prm.php?'); - expect(request.ca).to.equal('BID'); - expect(request.pubId).to.equal('001'); - expect(request.siteId).to.equal('111'); - expect(request.placementId).to.equal('1'); - expect(request.size[0]).to.equal(300); - expect(request.size[1]).to.equal(250); - expect(creds.method).to.equal('POST'); - }); - - it('Verify bid', () => { - featureForwardAdapter.callBids(slotConfigs); - ajaxStub.firstCall.args[1](JSON.stringify({ - html: 'FF Test Ad', - bidCpm: 0.555, - width: 300, - height: 250 - })); - let bid = bidManager.addBidResponse.firstCall.args[1]; - expect(bid.bidderCode).to.equal('featureforward'); - expect(bid.cpm).to.equal(0.555); - expect(bid.ad).to.equal('FF Test Ad'); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - }); - - it('Verify passback', () => { - featureForwardAdapter.callBids(slotConfigs); - // trigger a mock ajax callback with no bid. - ajaxStub.firstCall.args[1](null); - let placement = bidManager.addBidResponse.firstCall.args[0]; - let bid = bidManager.addBidResponse.firstCall.args[1]; - expect(placement).to.equal('test1_placement'); - expect(bid.bidderCode).to.equal('featureforward'); - expect(bid).to.not.have.property('ad'); - expect(bid).to.not.have.property('cpm'); - }); - - it('Verify passback when ajax call fails', () => { - ajaxStub.throws(); - featureForwardAdapter.callBids(slotConfigs); - let placement = bidManager.addBidResponse.firstCall.args[0]; - let bid = bidManager.addBidResponse.firstCall.args[1]; - expect(placement).to.equal('test1_placement'); - expect(bid.bidderCode).to.equal('featureforward'); - expect(bid).to.not.have.property('ad'); - expect(bid).to.not.have.property('cpm'); - }); -}); diff --git a/test/spec/modules/fidelityBidAdapter_spec.js b/test/spec/modules/fidelityBidAdapter_spec.js index 5777c6af8cd..28ea18aacac 100644 --- a/test/spec/modules/fidelityBidAdapter_spec.js +++ b/test/spec/modules/fidelityBidAdapter_spec.js @@ -1,195 +1,160 @@ -describe('fidelity adapter tests', function() { - const expect = require('chai').expect; - const adapter = require('modules/fidelityBidAdapter'); - const adLoader = require('src/adloader'); - const bidmanager = require('src/bidmanager'); - const STATUS = require('src/constants').STATUS; - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - - describe('creation of bid url', function () { - it('should be called', function () { - var stubLoadScript; - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - - var bidderRequest = { - bidderCode: 'fidelity', - bids: [ - { - bidId: 'bidId-123456-1', - bidder: 'fidelity', - params: { - zoneid: '37' - }, - placementCode: 'div-gpt-ad-123456-1' - }, - ] - }; +import { expect } from 'chai'; +import { spec } from 'modules/fidelityBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; - adapter().callBids(bidderRequest); - sinon.assert.called(stubLoadScript); +describe('FidelityAdapter', () => { + const adapter = newBidder(spec); - stubLoadScript.restore(); + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); }); + }); - it('should populate required parameters', function () { - var stubLoadScript; - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - - var bidderRequest = { - bidderCode: 'fidelity', - bids: [ - { - bidId: 'bidId-123456-1', - bidder: 'fidelity', - params: { - zoneid: '37', - }, - placementCode: 'div-gpt-ad-123456-1' - }, - ] - }; - - adapter().callBids(bidderRequest); - - stubLoadScript.restore(); + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'fidelity', + 'params': { + 'zoneid': '37', + 'floor': '0.05', + 'server': 't.fidelity-media.com', + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should populate required and optional parameters', function () { - var stubLoadScript; - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - - var bidderRequest = { - bidderCode: 'fidelity', - bids: [ - { - bidId: 'bidId-123456-1', - bidder: 'fidelity', - params: { - zoneid: '37', - server: 't.fidelity-media.com', - loc: 'http://locurl', - click: 'http://clickurl', - subid: '000' - }, - placementCode: 'div-gpt-ad-123456-1' - }, - ] + it('should return true when required params found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'zoneid': '37', }; - - adapter().callBids(bidderRequest); - - var requestURI = stubLoadScript.getCall(0).args[0]; - var parsedBidUrl = urlParse(requestURI); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - expect(parsedBidUrl.hostname).to.equal('t.fidelity-media.com'); - - expect(parsedBidUrlQueryString).to.have.property('zoneid').and.to.equal('37'); - expect(parsedBidUrlQueryString).to.have.property('impid').and.to.equal('bidId-123456-1'); - expect(parsedBidUrlQueryString).to.have.property('callback').and.to.equal('window.$$PREBID_GLOBAL$$.fidelityResponse'); - expect(parsedBidUrlQueryString).to.have.property('loc').and.to.equal('http://locurl'); - expect(parsedBidUrlQueryString).to.have.property('ct0').and.to.equal('http://clickurl'); - expect(parsedBidUrlQueryString).to.have.property('subid').and.to.equal('000'); - - stubLoadScript.restore(); + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - }); - describe('fidelityResponse', function () { - it('should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.fidelityResponse).to.exist.and.to.be.a('function'); + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'zoneid': 0, + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); + }); - it('should add empty bid response if no bids returned', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var bidderRequest = { - bidderCode: 'fidelity', - bids: [ - { - bidId: 'bidId-123456-1', - bidder: 'fidelity', - params: { - zoneid: '37' - }, - placementCode: 'div-gpt-ad-123456-1' + describe('buildRequests', () => { + let bidderRequest = { + bidderCode: 'fidelity', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + bidderRequestId: '178e34bad3658f', + bids: [ + { + bidder: 'fidelity', + params: { + zoneid: '37', + floor: '0.05', + server: 't.fidelity-media.com', }, - ] - }; - - // no bids returned in the response. - var response = { - 'id': '543210', - 'seatbid': [] - }; - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - // adapter needs to be called, in order for the stub to register. - adapter() - - $$PREBID_GLOBAL$$.fidelityResponse(response); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('div-gpt-ad-123456-1'); - expect(bidObject1.getStatusCode()).to.equal(2); - expect(bidObject1.bidderCode).to.equal('fidelity'); - - stubAddBidResponse.restore(); + placementCode: '/19968336/header-bid-tag-0', + sizes: [[300, 250], [320, 50]], + bidId: '2ffb201a808da7', + bidderRequestId: '178e34bad3658f', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + ], + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000 + }; + + it('should add source and verison to the tag', () => { + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const payload = request.data; + expect(payload.from).to.exist; + expect(payload.v).to.exist; + expect(payload.requestid).to.exist; + expect(payload.impid).to.exist; + expect(payload.zoneid).to.exist; + expect(payload.floor).to.exist; + expect(payload.charset).to.exist; + expect(payload.defloc).to.exist; + expect(payload.altloc).to.exist; + expect(payload.subid).to.exist; + expect(payload.flashver).to.exist; + expect(payload.tmax).to.exist; }); - it('should add a bid response for bid returned', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var bidderRequest = { - bidderCode: 'fidelity', - bids: [ - { - bidId: 'bidId-123456-1', - bidder: 'fidelity', - params: { - zoneid: '37' - }, - placementCode: 'div-gpt-ad-123456-1' - }, - ] - }; + it('sends bid request to ENDPOINT via GET', () => { + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.url).to.equal('//t.fidelity-media.com/delivery/hb.php'); + expect(request.method).to.equal('GET'); + }); + }) + + describe('interpretResponse', () => { + let response = { + 'id': '543210', + 'seatbid': [ { + 'bid': [ { + 'id': '1111111', + 'impid': 'bidId-123456-1', + 'price': 0.09, + 'adm': '', + 'width': 728, + 'height': 90, + } ] + } ] + }; + + it('should get correct bid response', () => { + let expectedResponse = [ + { + requestId: 'bidId-123456-1', + creativeId: 'bidId-123456-1', + cpm: 0.09, + width: 728, + height: 90, + ad: '', + netRevenue: true, + currency: 'USD', + ttl: 360, + } + ]; + + let result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); - // Returning a single bid in the response. - var response = { + it('handles nobid responses', () => { + let response = { 'id': '543210', - 'seatbid': [ { - 'bid': [ { - 'id': '1111111', - 'impid': 'bidId-123456-1', - 'price': 0.09, - 'adm': '<>', - 'height': 90, - 'width': 728 - } ] - } ] + 'seatbid': [ ] }; - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - // adapter needs to be called, in order for the stub to register. - adapter() - - $$PREBID_GLOBAL$$.fidelityResponse(response); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('div-gpt-ad-123456-1'); - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.bidderCode).to.equal('fidelity'); - expect(bidObject1.cpm).to.equal(0.09); - expect(bidObject1.height).to.equal(90); - expect(bidObject1.width).to.equal(728); - expect(bidObject1.ad).to.equal('<>'); + let result = spec.interpretResponse({ body: response }); + expect(result.length).to.equal(0); + }); + }); - stubAddBidResponse.restore(); + describe('user sync', () => { + const syncUrl = '//x.fidelity-media.com/delivery/matches.php?type=iframe'; + + it('should register the sync iframe', () => { + expect(spec.getUserSyncs({})).to.be.undefined; + expect(spec.getUserSyncs({iframeEnabled: false})).to.be.undefined; + 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(syncUrl); }); }); }); diff --git a/test/spec/modules/freewheelSSPBidAdapter_spec.js b/test/spec/modules/freewheelSSPBidAdapter_spec.js new file mode 100644 index 00000000000..33bd647efaa --- /dev/null +++ b/test/spec/modules/freewheelSSPBidAdapter_spec.js @@ -0,0 +1,191 @@ +import { expect } from 'chai'; +import { spec } from 'modules/freewheelSSPBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const ENDPOINT = '//ads.stickyadstv.com/www/delivery/swfIndex.php'; + +describe('freewheelSSP BidAdapter Test', () => { + 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', () => { + 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', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + wrong: 'missing zone id' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should add parameters to the tag', () => { + const request = spec.buildRequests(bidRequests); + 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'); + }); + + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.contain(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + }) + + describe('interpretResponse', () => { + 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', () => { + var request = spec.buildRequests(bidRequests); + + 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', () => { + var request = spec.buildRequests(formattedBidRequests); + + 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', () => { + var reqest = spec.buildRequests(formattedBidRequests); + let response = ''; + + let result = spec.interpretResponse(response, reqest); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/gammaBidAdapter_spec.js b/test/spec/modules/gammaBidAdapter_spec.js new file mode 100644 index 00000000000..1f3f225336e --- /dev/null +++ b/test/spec/modules/gammaBidAdapter_spec.js @@ -0,0 +1,119 @@ +import * as utils from 'src/utils'; +import { expect } from 'chai'; +import { spec } from 'modules/gammaBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('gammaBidAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'gamma', + 'params': { + siteId: '1465446377', + zoneId: '1515999290' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when require params are not passed', () => { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when params not passed correctly', () => { + bid.params.siteId = ''; + bid.params.zoneId = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'gamma', + 'params': { + siteId: '1465446377', + zoneId: '1515999290' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1' + } + ]; + + const request = spec.buildRequests(bidRequests); + + it('sends bid request to our endpoint via GET', () => { + expect(request.method).to.equal('GET'); + }); + + it('bidRequest url', () => { + expect(request.url).to.match(new RegExp(`hb.gammaplatform.com`)); + }); + }); + + describe('interpretResponse', () => { + let serverResponse = { + body: { + 'id': '23beaa6af6cdde', + 'bid': '5611802021800040585', + 'type': 'banner', + 'cur': 'USD', + 'seatbid': [{ + 'seat': '5611802021800040585', + 'bid': [{ + 'id': '1515999070', + 'impid': '1', + 'price': 0.45, + 'adm': '', + 'adid': '1515999070', + 'dealid': 'gax-paj2qarjf2g', + 'h': 250, + 'w': 300 + }] + }] + } + }; + + it('should get the correct bid response', () => { + let expectedResponse = [{ + 'requestId': '23beaa6af6cdde', + 'cpm': 0.45, + 'width': 300, + 'height': 250, + 'creativeId': '1515999070', + 'dealId': 'gax-paj2qarjf2g', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'ad': '' + }]; + let result = spec.interpretResponse(serverResponse); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + + it('handles empty bid response', () => { + let response = { + body: {} + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/getintentBidAdapter_spec.js b/test/spec/modules/getintentBidAdapter_spec.js index e66d2138eaf..17f9a95fec4 100644 --- a/test/spec/modules/getintentBidAdapter_spec.js +++ b/test/spec/modules/getintentBidAdapter_spec.js @@ -1,146 +1,142 @@ -import Adapter from '../../../modules/getintentBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import {expect} from 'chai'; - -var assert = require('chai').assert; - -describe('getintent media adapter test', () => { - let adapter; - - window.gi_hb = { - makeBid: function(bidRequest, callback) { - var pid = bidRequest.pid; - var tid = bidRequest.tid; - - if (pid == 'p1' || pid == 'p2') { - callback({ - ad: `Ad Markup ${pid} ${tid}`, - cpm: 2.71, - size: `${bidRequest.size}` - }, bidRequest); - } else if (pid == 'p3') { - callback({ - no_bid: 1 - }, bidRequest); - } else if (pid == 'p4') { - callback({ - vast_url: `http://test.com?pid=${pid}&tid=${tid}`, - cpm: 2.88, - size: `${bidRequest.size}` - }, bidRequest); +import { expect } from 'chai' +import { spec } from 'modules/getintentBidAdapter' + +describe('GetIntent Adapter Tests:', () => { + const bidRequests = [{ + bidId: 'bid12345', + params: { + pid: 'p1000', + tid: 't1000' + }, + sizes: [[300, 250]] + }, + { + bidId: 'bid54321', + params: { + pid: 'p1000', + tid: 't1000' + }, + sizes: [[50, 50], [100, 100]] + }] + const videoBidRequest = { + bidId: 'bid789', + params: { + pid: 'p1001', + tid: 't1001', + video: { + mimes: ['video/mp4', 'application/javascript'], + max_dur: 20, + api: [1, 2], + skippable: true } - } + }, + sizes: [300, 250], + mediaType: 'video' }; - function callOut() { - adapter.callBids({ - bidderCode: 'getintent', - bids: [ - { - bidder: 'getintent', - adUnitCode: 'test1', - sizes: [[320, 240]], - params: { - pid: 'p1', - tid: 't1', - cur: 'USD' - } - }, - { - bidder: 'getintent', - adUnitCode: 'test2', - sizes: [[720, 90]], - params: { - pid: 'p2', - tid: 't1', - cur: 'USD' - } - }, - { - bidder: 'getintent', - adUnitCode: 'test3', - sizes: [[400, 500]], - params: { - pid: 'p3', - tid: 't2', - cur: 'USD' - } - }, - { - bidder: 'getintent', - adUnitCode: 'test4', - mediaType: 'video', - sizes: [[480, 352]], - params: { - pid: 'p4', - tid: 't3', - cur: 'USD' - } - } - ] - }); - } - - beforeEach(() => { - adapter = new Adapter(); + it('Verify build request', () => { + const serverRequests = spec.buildRequests(bidRequests); + let serverRequest = serverRequests[0]; + expect(serverRequest.url).to.equal('//px.adhigh.net/rtb/direct_banner'); + expect(serverRequest.method).to.equal('GET'); + expect(serverRequest.data.bid_id).to.equal('bid12345'); + expect(serverRequest.data.pid).to.equal('p1000'); + expect(serverRequest.data.tid).to.equal('t1000'); + expect(serverRequest.data.size).to.equal('300x250'); + expect(serverRequest.data.is_video).to.equal(false); + serverRequest = serverRequests[1]; + expect(serverRequest.data.size).to.equal('50x50,100x100'); }); - afterEach(() => { + it('Verify build video request', () => { + const serverRequests = spec.buildRequests([videoBidRequest]); + let serverRequest = serverRequests[0]; + expect(serverRequest.url).to.equal('//px.adhigh.net/rtb/direct_vast'); + expect(serverRequest.method).to.equal('GET'); + expect(serverRequest.data.bid_id).to.equal('bid789'); + expect(serverRequest.data.pid).to.equal('p1001'); + expect(serverRequest.data.tid).to.equal('t1001'); + expect(serverRequest.data.size).to.equal('300x250'); + expect(serverRequest.data.is_video).to.equal(true); + expect(serverRequest.data.mimes).to.equal('video/mp4,application/javascript'); + expect(serverRequest.data.max_dur).to.equal(20); + expect(serverRequest.data.api).to.equal('1,2'); + expect(serverRequest.data.skippable).to.equal(true); }); - describe('adding bids to the manager', () => { - let firstBid; - let secondBid; - let thirdBid; - let videoBid; - - beforeEach(() => { - sinon.stub(bidManager, 'addBidResponse'); - callOut(); - firstBid = bidManager.addBidResponse.firstCall.args[1]; - secondBid = bidManager.addBidResponse.secondCall.args[1]; - thirdBid = bidManager.addBidResponse.thirdCall.args[1]; - videoBid = bidManager.addBidResponse.lastCall.args[1]; - }); - - afterEach(() => { - bidManager.addBidResponse.restore(); - }); - - it('was called four times', () => { - assert.strictEqual(bidManager.addBidResponse.callCount, 4); - }); + it('Verify parse response', () => { + const serverResponse = { + body: { + bid_id: 'bid12345', + cpm: 2.25, + currency: 'USD', + size: '300x250', + creative_id: '1000', + ad: 'Ad markup' + }, + headers: { + } + }; + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(2.25); + expect(bid.currency).to.equal('USD'); + expect(bid.creativeId).to.equal('1000'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.requestId).to.equal('bid12345'); + expect(bid.mediaType).to.equal('banner'); + expect(bid.ad).to.equal('Ad markup'); + }); - it('will respond to the first bid', () => { - expect(firstBid).to.have.property('ad', 'Ad Markup p1 t1'); - expect(firstBid).to.have.property('cpm', 2.71); - expect(firstBid).to.have.property('width', '320'); - expect(firstBid).to.have.property('height', '240'); - }); + it('Verify parse video response', () => { + const serverResponse = { + body: { + bid_id: 'bid789', + cpm: 3.25, + currency: 'USD', + size: '300x250', + creative_id: '2000', + vast_url: '//vast.xml/url' + }, + headers: { + } + }; + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(3.25); + expect(bid.currency).to.equal('USD'); + expect(bid.creativeId).to.equal('2000'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.requestId).to.equal('bid789'); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastUrl).to.equal('//vast.xml/url'); + }); - it('will respond to the second bid', () => { - expect(secondBid).to.have.property('ad', 'Ad Markup p2 t1'); - expect(secondBid).to.have.property('cpm', 2.71); - expect(secondBid).to.have.property('width', '720'); - expect(secondBid).to.have.property('height', '90'); - }); + it('Verify bidder code', () => { + expect(spec.code).to.equal('getintent'); + }); - it('wont respond to the third bid', () => { - expect(thirdBid).to.not.have.property('ad'); - expect(thirdBid).to.not.have.property('cpm'); - }); + it('Verify bidder aliases', () => { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('getintentAdapter'); + }); - it('will add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'getintent'); - expect(secondBid).to.have.property('bidderCode', 'getintent'); - expect(thirdBid).to.have.property('bidderCode', 'getintent'); - }); + it('Verify supported media types', () => { + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[0]).to.equal('video'); + expect(spec.supportedMediaTypes[1]).to.equal('banner'); + }); - it('will respond to the video bid', () => { - expect(videoBid).to.have.property('vastUrl', 'http://test.com?pid=p4&tid=t3'); - expect(videoBid).to.have.property('cpm', 2.88); - expect(videoBid).to.have.property('width', '480'); - expect(videoBid).to.have.property('height', '352'); - }); + it('Verify if bid request valid', () => { + expect(spec.isBidRequestValid(bidRequests[0])).to.equal(true); + expect(spec.isBidRequestValid(bidRequests[1])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect(spec.isBidRequestValid({ params: { test: 123 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { pid: 111, tid: 222 } })).to.equal(true); }); }); diff --git a/test/spec/modules/gjirafaBidAdapter_spec.js b/test/spec/modules/gjirafaBidAdapter_spec.js new file mode 100644 index 00000000000..17fbdc33591 --- /dev/null +++ b/test/spec/modules/gjirafaBidAdapter_spec.js @@ -0,0 +1,156 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gjirafaBidAdapter'; + +describe('gjirafaAdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with placementId, minCPM and minCPC params', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + placementId: 'test-div', + minCPM: 0.0001, + minCPC: 0.001 + } + })).to.equal(true); + }); + + it('bidRequest with only placementId param', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + placementId: 'test-div' + } + })).to.equal(true); + }); + + it('bidRequest with minCPM and minCPC params', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + minCPM: 0.0001, + minCPC: 0.001 + } + })).to.equal(true); + }); + + it('bidRequest with no placementId, minCPM or minCPC params', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + } + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'gjirafa', + 'params': { + 'placementId': '71-3' + }, + 'adUnitCode': 'hb-leaderboard', + 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', + 'sizes': [[728, 90], [980, 200], [980, 150], [970, 90], [970, 250]], + 'bidId': '10bdc36fe0b48c8', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' + }, + { + 'bidder': 'gjirafa', + 'params': { + 'minCPM': 0.0001, + 'minCPC': 0.001, + 'explicit': true + }, + 'adUnitCode': 'hb-inarticle', + 'transactionId': '8757194d-ea7e-4c06-abc0-cfe92bfc5295', + 'sizes': [[300, 250]], + 'bidId': '81a6dcb65e2bd9', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' + }]; + + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('GET'); + }); + }); + + it('bidRequest url', () => { + const endpointUrl = 'https://gjc.gjirafa.com/Home/GetBid'; + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.url).to.match(new RegExp(`${endpointUrl}`)); + }); + }); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.data).to.exists; + }); + }); + + it('bidRequest sizes', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].data.sizes).to.equal('728x90;980x200;980x150;970x90;970x250'); + expect(requests[1].data.sizes).to.equal('300x250'); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'GET', + 'url': 'https://gjc.gjirafa.com/Home/GetBid', + 'data': { + 'gjid': 2323007, + 'sizes': '728x90;980x200;980x150;970x90;970x250', + 'configId': '71-3', + 'minCPM': 0, + 'minCPC': 0, + 'allowExplicit': 0, + 'referrer': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'requestid': '26ee8fe87940da7', + 'bidid': '2962dbedc4768bf' + } + }; + + const bidResponse = { + body: [{ + 'CPM': 1, + 'Width': 728, + 'Height': 90, + 'Referrer': 'https://example.com/', + 'Ad': 'test ad', + 'CreativeId': '123abc', + 'NetRevenue': false, + 'Currency': 'EUR', + 'TTL': 360 + }], + headers: {} + }; + + it('all keys present', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + let keys = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'creativeId', + 'currency', + 'netRevenue', + 'ttl', + 'referrer', + 'ad' + ]; + + let resultKeys = Object.keys(result[0]); + resultKeys.forEach(function(key) { + expect(keys.indexOf(key) !== -1).to.equal(true); + }); + }) + }); +}); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index b90a1a48b15..276972a163f 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -1,295 +1,160 @@ -import {expect} from 'chai'; -import Adapter from '../../../modules/gumgumBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import adLoader from '../../../src/adloader'; -import * as utils from '../../../src/utils'; -import { STATUS } from '../../../src/constants'; +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/gumgumBidAdapter'; -describe('gumgum adapter', () => { - 'use strict'; +const ENDPOINT = 'https://g2.gumgum.com/hbid/imp'; - let adapter; - let sandbox; +describe('gumgumAdapter', () => { + const adapter = newBidder(spec); - const TEST = { - PUBLISHER_IDENTITY: 'ggumtest', - BIDDER_CODE: 'gumgum', - PLACEMENT: 'placementId', - CPM: 2 - }; - const bidderRequest = { - bidderCode: TEST.BIDDER_CODE, - bids: [{ // in-screen - bidId: 'InScreenBidId', - bidder: TEST.BIDDER_CODE, - placementCode: TEST.PLACEMENT, - sizes: [ [728, 90] ], - params: { - inScreen: TEST.PUBLISHER_IDENTITY - } - }, { // in-image - bidId: 'InImageBidId', - bidder: TEST.BIDDER_CODE, - placementCode: TEST.PLACEMENT, - sizes: [ [728, 90] ], - params: { - inImage: TEST.PUBLISHER_IDENTITY - } - }, { // native - bidId: 'NativeBidId', - bidder: TEST.BIDDER_CODE, - placementCode: TEST.PLACEMENT, - sizes: [ [728, 90] ], - params: { - native: 10 - } - }, { // slot - bidId: 'InSlotBidId', - bidder: TEST.BIDDER_CODE, - placementCode: TEST.PLACEMENT, - sizes: [ [728, 90] ], - params: { - inSlot: 10 - } - }, { // no identity - bidId: 'NoIdentityBidId', - bidder: TEST.BIDDER_CODE, - placementCode: TEST.PLACEMENT, - sizes: [ [728, 90] ] - }] - }; - const pageParams = { - 'pvid': 'PVID' - }; - const bidderResponse = { - 'ad': { - 'id': 1, - 'width': 728, - 'height': 90, - 'markup': '
some fancy ad
', - 'ii': true, - 'du': 'http://example.com/', - 'price': TEST.CPM, - 'impurl': 'http://example.com/' - }, - 'pag': pageParams - }; - - function mockBidResponse(response) { - sandbox.stub(bidManager, 'addBidResponse'); - sandbox.stub(adLoader, 'loadScript'); - adapter.callBids(bidderRequest); - $$PREBID_GLOBAL$$.handleGumGumCB['InScreenBidId'](response); - return bidManager.addBidResponse.firstCall.args[1]; - } - - beforeEach(() => { - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('DigiTrust params', () => { - beforeEach(() => { - sandbox.stub(adLoader, 'loadScript'); + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); }); + }); - it('should send digiTrust params', () => { - window.DigiTrust = { - getUser: function() {} + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'gumgum', + 'params': { + 'inScreen': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'inSlot': '789' }; - sandbox.stub(window.DigiTrust, 'getUser', () => - ({ - success: true, - identity: { - privacy: {optout: false}, - id: 'testId' - } - }) - ); - adapter.callBids(bidderRequest); - expect(adLoader.loadScript.firstCall.args[0]).to.include('&dt=testId'); - delete window.DigiTrust; + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should not send DigiTrust params when DigiTrust is not loaded', () => { - adapter.callBids(bidderRequest); - expect(adLoader.loadScript.firstCall.args[0]).to.not.include('&dt'); - }); - - it('should not send DigiTrust params due to opt out', () => { - window.DigiTrust = { - getUser: function() {} + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 }; - sandbox.stub(window.DigiTrust, 'getUser', () => - ({ - success: true, - identity: { - privacy: {optout: true}, - id: 'testId' - } - }) - ); - - adapter.callBids(bidderRequest); - expect(adLoader.loadScript.firstCall.args[0]).to.not.include('&dt'); - delete window.DigiTrust; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); + }); - it('should not send DigiTrust params on failure', () => { - window.DigiTrust = { - getUser: function() {} + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'gumgum', + 'params': { + 'inSlot': '9' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e' + } + ]; + + it('sends bid request to ENDPOINT via GET', () => { + const requests = spec.buildRequests(bidRequests); + const request = requests[0]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + expect(request.id).to.equal('30b31c1838de1e'); + }); + }) + + describe('interpretResponse', () => { + let serverResponse = { + 'ad': { + 'id': 29593, + 'width': 300, + 'height': 250, + 'ipd': 2000, + 'markup': '

I am an ad

', + 'ii': true, + 'du': null, + 'price': 0, + 'zi': 0, + 'impurl': 'http://g2.gumgum.com/ad/view', + 'clsurl': 'http://g2.gumgum.com/ad/close' + }, + 'pag': { + 't': 'ggumtest', + 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + 'css': 'html { overflow-y: auto }', + 'js': 'console.log("environment", env);' + }, + 'thms': 10000 + } + let bidRequest = { + id: 12345, + sizes: [[300, 250]], + url: ENDPOINT, + method: 'GET', + pi: 3 + } + + it('should get correct bid response', () => { + let expectedResponse = { + 'ad': '

I am an ad

', + 'cpm': 0, + 'creativeId': 29593, + 'currency': 'USD', + 'height': '250', + 'netRevenue': true, + 'requestId': 12345, + 'width': '300', + // dealId: DEAL_ID, + // referrer: REFERER, + ttl: 60 }; - sandbox.stub(window.DigiTrust, 'getUser', () => - ({ - success: false, - identity: { - privacy: {optout: false}, - id: 'testId' + expect(spec.interpretResponse({ body: serverResponse }, bidRequest)).to.deep.equal([expectedResponse]); + }); + + it('handles nobid responses', () => { + let response = { + 'ad': {}, + 'pag': { + 't': 'ggumtest', + 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + 'css': 'html { overflow-y: auto }', + 'js': 'console.log("environment", env);' + }, + 'thms': 10000 + } + let result = spec.interpretResponse({ body: response }, bidRequest); + expect(result.length).to.equal(0); + }); + }) + describe('getUserSyncs', () => { + const syncOptions = { + 'iframeEnabled': 'true' + } + const response = { + 'pxs': { + 'scr': [ + { + 't': 'i', + 'u': 'https://c.gumgum.com/images/pixel.gif' + }, + { + 't': 'f', + 'u': 'https://www.nytimes.com/' } - }) - ); - - adapter.callBids(bidderRequest); - expect(adLoader.loadScript.firstCall.args[0]).to.not.include('&dt'); - delete window.DigiTrust; - }); - }); - - describe('callBids', () => { - beforeEach(() => { - sandbox.stub(adLoader, 'loadScript'); - adapter.callBids(bidderRequest); - }); - - it('calls the endpoint once per valid bid', () => { - sinon.assert.callCount(adLoader.loadScript, 4); - }); - - it('includes required browser data', () => { - const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]); - endpointRequest.to.include('vw'); - endpointRequest.to.include('vh'); - endpointRequest.to.include('sw'); - endpointRequest.to.include('sh'); - }); - - it('includes the global bid timeout', () => { - const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]); - endpointRequest.to.include(`tmax=${$$PREBID_GLOBAL$$.cbTimeout}`); - }); - - it('includes the publisher identity', () => { - const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]); - endpointRequest.to.include('t=' + TEST.PUBLISHER_IDENTITY); - }); - - it('first call should be in-screen', () => { - expect(adLoader.loadScript.firstCall.args[0]).to.include('pi=2'); - }); - - it('second call should be in-image', () => { - expect(adLoader.loadScript.secondCall.args[0]).to.include('pi=1'); - }); - - it('third call should be native', () => { - expect(adLoader.loadScript.thirdCall.args[0]).to.include('pi=5'); - }); - - it('last call should be slot', () => { - expect(adLoader.loadScript.lastCall.args[0]).to.include('pi=3'); - }); - }); - - describe('handleGumGumCB[...]', () => { - it('exists and is function', () => { - expect($$PREBID_GLOBAL$$.handleGumGumCB['InScreenBidId']).to.exist.and.to.be.a('function'); - }); - }); - - describe('respond with a successful bid', () => { - let successfulBid; - - beforeEach(() => { - successfulBid = mockBidResponse(bidderResponse); - }); - - it('adds one bid', () => { - sinon.assert.calledOnce(bidManager.addBidResponse); - }); - - it('passes the correct placement code as the first param', () => { - const [ placementCode ] = bidManager.addBidResponse.firstCall.args; - expect(placementCode).to.eql(TEST.PLACEMENT); - }); - - it('has a GOOD status code', () => { - const STATUS_CODE = successfulBid.getStatusCode(); - expect(STATUS_CODE).to.eql(STATUS.GOOD); - }); - - it('uses the CPM returned by the server', () => { - expect(successfulBid).to.have.property('cpm', TEST.CPM); - }); - - it('has an ad', () => { - expect(successfulBid).to.have.property('ad'); - }); - - it('has the size specified by the server', () => { - expect(successfulBid).to.have.property('width', 728); - expect(successfulBid).to.have.property('height', 90); - }); - }); - - describe('respond with an empty bid', () => { - let noBid; - - beforeEach(() => { - noBid = mockBidResponse(undefined); - }); - - it('adds one bid', () => { - sinon.assert.calledOnce(bidManager.addBidResponse); - }); - - it('has a NO_BID status code', () => { - expect(noBid.getStatusCode()).to.eql(STATUS.NO_BID); - }); - - it('passes the correct placement code as the first parameter', () => { - const [ placementCode ] = bidManager.addBidResponse.firstCall.args; - expect(placementCode).to.eql(TEST.PLACEMENT); - }); - - it('adds the bidder code to the bid object', () => { - expect(noBid).to.have.property('bidderCode', TEST.BIDDER_CODE); - }); - }); - - describe('refresh throttle', () => { - beforeEach(() => { - mockBidResponse(bidderResponse); - }); - - afterEach(() => { - if (utils.logWarn.restore) { - utils.logWarn.restore(); + ] } - }); - - it('warns about the throttle limit', function() { - sinon.spy(utils, 'logWarn'); - // call all the binds again - adapter.callBids(bidderRequest); - // the timeout for in-screen should stop one bid request - const warning = expect(utils.logWarn.args[0][0]); - warning.to.include(TEST.PLACEMENT); - warning.to.include('inScreen'); - }); - }); + } + let result = spec.getUserSyncs(syncOptions, [{ body: response }]); + expect(result[0].type).to.equal('image') + expect(result[1].type).to.equal('iframe') + }) }); diff --git a/test/spec/modules/hiromediaBidAdapter_spec.js b/test/spec/modules/hiromediaBidAdapter_spec.js deleted file mode 100644 index c1ed4ee6e11..00000000000 --- a/test/spec/modules/hiromediaBidAdapter_spec.js +++ /dev/null @@ -1,331 +0,0 @@ -import { expect } from 'chai'; -import urlParse from 'url-parse'; - -import Adapter from 'modules/hiromediaBidAdapter'; -import bidmanager from 'src/bidmanager'; -import { STATUS } from 'src/constants'; -import * as utils from 'src/utils'; - -describe('hiromedia adapter', function () { - const BIDDER_CODE = 'hiromedia'; - const DEFAULT_ENDPOINT = 'https://hb-rtb.ktdpublishers.com/bid/get'; - - let adapter; - let sandbox; - let xhr; - let addBidResponseStub; - let hasValidBidRequestSpy; - let placementId = 0; - - window.$$PREBID_GLOBAL$$ = window.$$PREBID_GLOBAL$$ || {}; - - beforeEach(() => { - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - - // Used to spy on bid requests - xhr = sandbox.useFakeXMLHttpRequest(); - - // Used to spy on bid responses - addBidResponseStub = sandbox.stub(bidmanager, 'addBidResponse'); - - // Used to spy on bid validation - hasValidBidRequestSpy = sandbox.spy(utils, 'hasValidBidRequest'); - - placementId = 0; - }); - - afterEach(() => { - sandbox.restore(); - }); - - // Helper function that asserts that no bidding activity (requests nor responses) - // was made during a test. - const assertNoBids = () => { - expect(xhr.requests.length).to.be.equal(0); - sinon.assert.notCalled(addBidResponseStub); - }; - - // Helper function to generate a 'mock' bid object - const makePlacement = (size) => { - placementId += 1; - - return { - bidder: BIDDER_CODE, - sizes: [size], - params: { - accountId: '1337' - }, - placementCode: 'div-gpt-ad-12345-' + placementId - }; - }; - - // 300x250 are in the allowed size by default - const tilePlacement = () => makePlacement([300, 250]); - - // anything else should have no bid by default - const leaderPlacement = () => makePlacement([728, 90]); - - describe('callbids', () => { - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - - it('tolerates empty arguments', () => { - expect(adapter.callBids).to.not.throw(Error); - assertNoBids(); - }); - - it('tolerates empty params', () => { - expect(adapter.callBids.bind(adapter, {})).to.not.throw(Error); - assertNoBids(); - }); - - it('tolerates empty bids', () => { - expect(adapter.callBids.bind(adapter, {bids: []})).to.not.throw(Error); - assertNoBids(); - }); - - it('invokes a bid request per placement', () => { - const expectedRequests = [{ - placementCode: 'div-gpt-ad-12345-1', - selectedSize: '300x250' - }, { - placementCode: 'div-gpt-ad-12345-2', - selectedSize: '728x90' - }, { - placementCode: 'div-gpt-ad-12345-3', - selectedSize: '300x250' - }]; - - const params = { - bids: [tilePlacement(), leaderPlacement(), tilePlacement()] - }; - - adapter.callBids(params); - expect(xhr.requests.length).to.equal(3); - sinon.assert.notCalled(addBidResponseStub); - sinon.assert.calledThrice(hasValidBidRequestSpy); - - expectedRequests.forEach(function(request, index) { - expect(hasValidBidRequestSpy.returnValues[index]).to.be.equal(true); - - // validate request - const bidRequest = xhr.requests[index].url; - const defaultBidUrl = urlParse(DEFAULT_ENDPOINT); - const bidUrl = urlParse(bidRequest, true); - const query = bidUrl.query; - - expect(bidUrl.hostname).to.equal(defaultBidUrl.hostname); - expect(bidUrl.pathname).to.equal(defaultBidUrl.pathname); - - expect(query).to.have.property('adapterVersion').and.to.equal('3'); - expect(query).to.have.property('placementCode').and.to.equal(request.placementCode); - expect(query).to.have.property('accountId').and.to.equal('1337'); - expect(query).to.have.property('selectedSize').and.to.equal(request.selectedSize); - expect(query).to.not.have.property('additionalSizes'); - expect(query).to.have.property('domain').and.to.equal(window.top.location.hostname); - }); - }); - - // Test additionalSizes parameter - it('attaches multiple sizes to additionalSizes', () => { - const placement = tilePlacement(); - - // Append additional - placement.sizes.push([300, 600]); - placement.sizes.push([300, 900]); - - const params = { - bids: [placement] - }; - - adapter.callBids(params); - expect(xhr.requests.length).to.be.equal(1); - - const bidRequest = xhr.requests[0].url; - const bidUrl = urlParse(bidRequest, true); - const query = bidUrl.query; - - expect(query).to.have.property('selectedSize').and.to.equal('300x250'); - expect(query).to.have.property('additionalSizes').and.to.equal('300x600,300x900'); - }); - - // Test `params.accountId` validation - it('invalidates bids with no id', () => { - const placement = tilePlacement(); - delete placement.params; - - const params = { - bids: [placement] - }; - - adapter.callBids(params); - expect(xhr.requests.length).to.be.equal(0); - sinon.assert.calledOnce(hasValidBidRequestSpy); - sinon.assert.calledOnce(addBidResponseStub); - - expect(hasValidBidRequestSpy.returnValues[0]).to.be.equal(false); - const placementCode = addBidResponseStub.getCall(0).args[0]; - const bidObject = addBidResponseStub.getCall(0).args[1]; - - expect(placementCode).to.be.equal('div-gpt-ad-12345-1'); - expect(bidObject.getStatusCode()).to.be.equal(STATUS.NO_BID); - }); - - // Test `params.bidUrl` - it('accepts a custom bid endpoint url', () => { - const placement = tilePlacement(); - placement.params.bidUrl = DEFAULT_ENDPOINT + '?someparam=value'; - - const params = { - bids: [placement] - }; - - adapter.callBids(params); - expect(xhr.requests.length).to.be.equal(1); - - const bidRequest = xhr.requests[0].url; - const defaultBidUrl = urlParse(DEFAULT_ENDPOINT); - const bidUrl = urlParse(bidRequest, true); - const query = bidUrl.query; - - expect(bidUrl.hostname).to.equal(defaultBidUrl.hostname); - expect(bidUrl.pathname).to.equal(defaultBidUrl.pathname); - - expect(query).to.have.property('someparam').and.to.equal('value'); - }); - }); - - describe('response handler', () => { - let server; - - beforeEach(() => { - server = sandbox.useFakeServer(); - }); - - const assertSingleFailedBidResponse = () => { - sinon.assert.calledOnce(addBidResponseStub); - const placementCode = addBidResponseStub.getCall(0).args[0]; - const bidObject = addBidResponseStub.getCall(0).args[1]; - - expect(placementCode).to.be.equal('div-gpt-ad-12345-1'); - expect(bidObject.getStatusCode()).to.be.equal(STATUS.NO_BID); - }; - - it('tolerates an empty response', () => { - server.respondWith(''); - adapter.callBids({bids: [tilePlacement()]}); - server.respond(); - - assertSingleFailedBidResponse(); - }); - - it('tolerates a response error', () => { - server.respondWith([500, {}, '']); - adapter.callBids({bids: [tilePlacement()]}); - server.respond(); - - assertSingleFailedBidResponse(); - }); - - it('tolerates an invalid response', () => { - server.respondWith('not json'); - adapter.callBids({bids: [tilePlacement()]}); - server.respond(); - - assertSingleFailedBidResponse(); - }); - - it('adds a bid reponse for each pending bid', () => { - const responses = [{ - width: '300', - height: '250', - cpm: 0.4, - ad: '' - }, { - width: '728', - height: '90', - cpm: 0.4, - ad: '' - }]; - - let id = 0; - server.respondWith((request) => { - request.respond(200, {}, JSON.stringify(responses[id])); - id += 1; - }); - - const params = { - bids: [tilePlacement(), leaderPlacement()] - }; - - adapter.callBids(params); - server.respond(); - - expect(server.requests.length).to.be.equal(2); - sinon.assert.calledTwice(addBidResponseStub); - - responses.forEach((expectedResponse, i) => { - const placementCode = addBidResponseStub.getCall(i).args[0]; - const bidObject = addBidResponseStub.getCall(i).args[1]; - - expect(placementCode).to.be.equal('div-gpt-ad-12345-' + (i + 1)); - - expect(bidObject.getStatusCode()).to.be.equal(STATUS.GOOD); - expect(bidObject).to.have.property('cpm').and.to.equal(expectedResponse.cpm); - expect(bidObject).to.have.property('ad').and.to.equal(expectedResponse.ad); - expect(bidObject).to.have.property('width').and.to.equal(expectedResponse.width); - expect(bidObject).to.have.property('height').and.to.equal(expectedResponse.height); - }); - }); - - // We want to check that responses are added according to a sampling value, - // this is possible by stubbing `Math.random`, to ensure the effect is - // limited to the area we check, we create a self destructing stub which - // restores itself once called. - it('adds responses according to the sampling defined in the response', () => { - const response = { - cpm: 0.4, - chance: 0.25, - ad: '' - }; - - // List of "random" values. We check that the first two pass and the last - // one is skipped. - const randomValues = [0.2, 0.3]; - let randomIndex = 0; - - server.respondWith((request) => { - const mathRandomStub = sandbox.stub(Math, 'random', function () { - const randomValue = randomValues[randomIndex]; - - randomIndex += 1; - mathRandomStub.restore(); // self destruct on call - - return randomValue; - }); - - request.respond(200, {}, JSON.stringify(response)); - - mathRandomStub.restore(); - }); - - const params = { - bids: [tilePlacement()] - }; - - adapter.callBids(params); - adapter.callBids(params); - server.respond(); - - sinon.assert.calledTwice(addBidResponseStub); - - const firstBidObject = addBidResponseStub.getCall(0).args[1]; - const secondBidObject = addBidResponseStub.getCall(1).args[1]; - - expect(firstBidObject.getStatusCode()).to.be.equal(STATUS.GOOD); - expect(secondBidObject.getStatusCode()).to.be.equal(STATUS.NO_BID); - }); - }); -}); diff --git a/test/spec/modules/huddledmassesBidAdapter_spec.js b/test/spec/modules/huddledmassesBidAdapter_spec.js new file mode 100644 index 00000000000..f98bc06d0da --- /dev/null +++ b/test/spec/modules/huddledmassesBidAdapter_spec.js @@ -0,0 +1,119 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/huddledmassesBidAdapter'; + +describe('HuddledmassesAdapter', () => { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'huddledmasses', + bidderRequestId: '145e1d6a7837c9', + params: { + placement_id: 0 + }, + placementCode: 'placementid_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + sizes: [[300, 250]], + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + }; + + describe('isBidRequestValid', () => { + it('Should return true when placement_id can be cast to a number, and when at least one of the sizes passed is allowed', () => { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when placement_id is not a number', () => { + bid.params.placement_id = 'aaa'; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('Should return false when the sizes are not allowed', () => { + bid.sizes = [[1, 1]]; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', () => { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', () => { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', () => { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', () => { + expect(serverRequest.url).to.equal('//huddledmassessupply.com/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', () => { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + let placements = data['placements']; + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.all.keys('placementId', 'bidId', 'sizes'); + expect(placement.placementId).to.be.a('number'); + expect(placement.bidId).to.be.a('string'); + expect(placement.sizes).to.be.an('array'); + } + }); + it('Returns empty data if no valid requests are passed', () => { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', () => { + let resObject = { + body: [ { + requestId: '123', + 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', () => { + 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'); + 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'); + } + it('Returns an empty array if invalid response is passed', () => { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); + + describe('getUserSyncs', () => { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', () => { + 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('//huddledmassessupply.com/?c=o&m=cookie'); + }); + }); +}); diff --git a/test/spec/modules/iasBidAdapter_spec.js b/test/spec/modules/iasBidAdapter_spec.js new file mode 100644 index 00000000000..4f335ab22ba --- /dev/null +++ b/test/spec/modules/iasBidAdapter_spec.js @@ -0,0 +1,190 @@ +import { expect } from 'chai'; +import { spec } from 'modules/iasBidAdapter'; + +describe('iasBidAdapter is an adapter that', () => { + it('has the correct bidder code', () => { + expect(spec.code).to.equal('ias'); + }); + describe('has a method `isBidRequestValid` that', () => { + it('exists', () => { + expect(spec.isBidRequestValid).to.be.a('function'); + }); + it('returns false if bid params misses `pubId`', () => { + expect(spec.isBidRequestValid( + { + params: { + adUnitPath: 'someAdUnitPath' + } + })).to.equal(false); + }); + it('returns false if bid params misses `adUnitPath`', () => { + expect(spec.isBidRequestValid( + { + params: { + pubId: 'somePubId' + } + })).to.equal(false); + }); + it('returns true otherwise', () => { + expect(spec.isBidRequestValid( + { + params: { + adUnitPath: 'someAdUnitPath', + pubId: 'somePubId', + someOtherParam: 'abc' + } + })).to.equal(true); + }); + }); + + describe('has a method `buildRequests` that', () => { + it('exists', () => { + expect(spec.buildRequests).to.be.a('function'); + }); + describe('given bid requests, returns a `ServerRequest` instance that', () => { + let bidRequests, IAS_HOST; + beforeEach(() => { + IAS_HOST = '//pixel.adsafeprotected.com/services/pub'; + bidRequests = [ + { + adUnitCode: 'one-div-id', + auctionId: 'someAuctionId', + bidId: 'someBidId', + 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: 'someBidId', + bidder: 'ias', + bidderRequestId: 'someBidderRequestId', + params: { + pubId: '1234', + adUnitPath: '/d/e/f' + }, + sizes: [ + [50, 60] + ], + transactionId: 'someTransactionId' + } + ]; + }); + it('has property `method` of `GET`', () => { + expect(spec.buildRequests(bidRequests)).to.deep.include({ + method: 'GET' + }); + }); + it('has property `url` to be the correct IAS endpoint', () => { + expect(spec.buildRequests(bidRequests)).to.deep.include({ + url: IAS_HOST + }); + }); + describe('has property `data` that is an encode query string containing information such as', () => { + let val; + const ANID_PARAM = 'anId'; + const SLOT_PARAM = 'slot'; + const SLOT_ID_PARAM = 'id'; + const SLOT_SIZE_PARAM = 'ss'; + const SLOT_AD_UNIT_PATH_PARAM = 'p'; + + beforeEach(() => val = decodeURI(spec.buildRequests(bidRequests).data)); + it('publisher id', () => { + expect(val).to.have.string(`${ANID_PARAM}=1234`); + }); + it('ad slot`s id, size and ad unit path', () => { + expect(val).to.have.string(`${SLOT_PARAM}={${SLOT_ID_PARAM}:one-div-id,${SLOT_SIZE_PARAM}:[10.20,300.400],${SLOT_AD_UNIT_PATH_PARAM}:/a/b/c}`); + expect(val).to.have.string(`${SLOT_PARAM}={${SLOT_ID_PARAM}:two-div-id,${SLOT_SIZE_PARAM}:[50.60],${SLOT_AD_UNIT_PATH_PARAM}:/d/e/f}`); + }); + it('window size', () => { + expect(val).to.match(/.*wr=[0-9]*\.[0-9]*/); + }); + it('screen size', () => { + expect(val).to.match(/.*sr=[0-9]*\.[0-9]*/); + }); + }); + it('has property `bidRequest` that is the first passed in bid request', () => { + expect(spec.buildRequests(bidRequests)).to.deep.include({ + bidRequest: bidRequests[0] + }); + }); + }); + }); + describe('has a method `interpretResponse` that', () => { + it('exists', () => { + expect(spec.interpretResponse).to.be.a('function'); + }); + describe('returns a list of bid response that', () => { + let bidResponse, slots; + beforeEach(() => { + 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'] + }; + const serverResponse = { + body: { + brandSafety: { + adt: 'adtVal', + alc: 'alcVal', + dlm: 'dlmVal', + drg: 'drgVal', + hat: 'hatVal', + off: 'offVal', + vio: 'vioVal' + }, + fr: 'false', + slots: slots + }, + headers: {} + }; + bidResponse = spec.interpretResponse(serverResponse, request); + }); + it('has IAS keyword `adt` as property', () => { + expect(bidResponse[0]).to.deep.include({ adt: 'adtVal' }); + }); + it('has IAS keyword `alc` as property', () => { + expect(bidResponse[0]).to.deep.include({ alc: 'alcVal' }); + }); + it('has IAS keyword `dlm` as property', () => { + expect(bidResponse[0]).to.deep.include({ dlm: 'dlmVal' }); + }); + it('has IAS keyword `drg` as property', () => { + expect(bidResponse[0]).to.deep.include({ drg: 'drgVal' }); + }); + it('has IAS keyword `hat` as property', () => { + expect(bidResponse[0]).to.deep.include({ hat: 'hatVal' }); + }); + it('has IAS keyword `off` as property', () => { + expect(bidResponse[0]).to.deep.include({ off: 'offVal' }); + }); + it('has IAS keyword `vio` as property', () => { + expect(bidResponse[0]).to.deep.include({ vio: 'vioVal' }); + }); + it('has IAS keyword `fr` as property', () => { + expect(bidResponse[0]).to.deep.include({ fr: 'false' }); + }); + it('has property `slots`', () => { + expect(bidResponse[0]).to.deep.include({ slots: slots }); + }); + }); + }); +}); diff --git a/test/spec/modules/imonomyBidAdapter_spec.js b/test/spec/modules/imonomyBidAdapter_spec.js deleted file mode 100644 index e27a0d69a60..00000000000 --- a/test/spec/modules/imonomyBidAdapter_spec.js +++ /dev/null @@ -1,109 +0,0 @@ -import Adapter from '../../../modules/imonomyBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import {expect} from 'chai'; -import adLoader from '../../../src/adloader'; - -var CONSTANTS = require('../../../src/constants'); - -describe('imonomy adapter test', () => { - var utils = require('src/utils'); - let adapter; - let stubAddBidResponse; - let sandbox; - - let validBid = { - bidderCode: 'imonomy', - bids: [ - { - bidder: 'imonomy', - placementCode: 'foo', - bidId: 'foo', - sizes: [[300, 250]], - params: { - publisher_id: '14567721164', - } - } - ] - }; - - let validResponse = { - ads: [ - { - impression_id: 'foo', - cpm: 1.12, - creative: '' - } - ] - }; - - beforeEach(() => { - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - stubAddBidResponse.restore(); - sandbox.restore(); - }); - - describe('dealing with diffrent situations', () => { - let server; - var stubGetUniqueIdentifierStr = sinon.spy(utils, 'getUniqueIdentifierStr'); - beforeEach(() => { - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - stubAddBidResponse.restore(); - sandbox.restore(); - stubGetUniqueIdentifierStr.restore(); - }); - - it('no bid if cdb handler responds with no bid empty string response', (done) => { - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.NO_BID }); - done(); - }); - - sandbox.stub(adLoader, 'loadScript'); - adapter.callBids(validBid); - var callbackName = '_hb_' + stubGetUniqueIdentifierStr.returnValues[0] - $$PREBID_GLOBAL$$[callbackName]({}) - }); - - it('adds bid for valid request', (done) => { - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.GOOD }); - done(); - }); - - sandbox.stub(adLoader, 'loadScript'); - adapter.callBids(validBid); - var callbackName = '_hb_' + stubGetUniqueIdentifierStr.returnValues[0] - $$PREBID_GLOBAL$$[callbackName](validResponse) - }); - - it('adds bid for valid request with UM', (done) => { - stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { - expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.GOOD }); - done(); - }); - - sandbox.stub(adLoader, 'loadScript'); - adapter.callBids(validBid); - var callbackName = '_hb_' + stubGetUniqueIdentifierStr.returnValues[0] - $$PREBID_GLOBAL$$[callbackName](validResponseUM) - }); - }); -}); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 5b0a9d37d57..cd68418d6c7 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,599 +1,332 @@ -describe('improvedigital adapter tests', function () { - const expect = require('chai').expect; - const Adapter = require('modules/improvedigitalBidAdapter'); - const bidmanager = require('src/bidmanager'); - const adloader = require('src/adloader'); - const constants = require('src/constants.json'); - var bidfactory = require('src/bidfactory'); - var utils = require('src/utils.js'); - - var improveDigitalAdapter, - sandbox, - bidsRequestedOriginal; +import { expect } from 'chai'; +import { ImproveDigitalAdServerJSClient, spec } from 'modules/improvedigitalBidAdapter'; +import { config } from 'src/config'; +import { userSync } from 'src/userSync'; + +describe('Improve Digital Adapter Tests', function () { + let idClient = new ImproveDigitalAdServerJSClient('hb'); + + const METHOD = 'GET'; + const URL = '//ad.360yield.com/hb'; + const PARAM_PREFIX = 'jsonp='; const simpleBidRequest = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - placementId: 1012544 - } - } - ] + bidder: 'improvedigital', + params: { + placementId: 1053688 + }, + adUnitCode: 'div-gpt-ad-1499748733608-0', + transactionId: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', + bidId: '33e9500b21129f', + bidderRequestId: '2772c1e566670b', + auctionId: '192721e36a0239' }; const simpleSmartTagBidRequest = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - publisherId: 1032, - placementKey: 'data_team_test_hb_smoke_test' - } - } - ] + bidder: 'improvedigital', + bidId: '1a2b3c', + placementCode: 'placement1', + params: { + publisherId: 1032, + placementKey: 'data_team_test_hb_smoke_test' + } }; - const keyValueBidRequest = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - placementId: 1012546, - keyValues: { - hbkv: ['01'] - } - } - } - ] - }; + describe('isBidRequestValid', () => { + it('should return false when no bid', () => { + expect(spec.isBidRequestValid()).to.equal(false); + }); - const sizeBidRequest = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - placementId: 1012545, - size: { - w: 800, - h: 600 - } - } - } - ] - }; + it('should return false when no bid.params', () => { + let bid = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - const twoAdSlots = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - placementId: 1012544, - } - }, - { - bidId: '4d5e6f', - placementCode: 'placement2', - params: { - placementId: 1012545, - size: { - w: 800, - h: 600 - } - } - } - ] - }; + it('should return false when both placementId and placementKey + publisherId are missing', () => { + let bid = { 'params': {} }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - const threeAdSlots = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', + it('should return false when only one of placementKey and publisherId is present', () => { + let bid = { params: { - placementId: 1012544, - } - }, - { - bidId: '4d5e6f', - placementCode: 'placement2', - params: { - placementId: 1012545, - size: { - w: 800, - h: 600 - } + publisherId: 1234 } - }, - { - bidId: '7g8h9i', - placementCode: 'placement3', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid = { params: { - placementId: 1012546, - keyValues: { - hbkv: ['01'] - } + placementKey: 'xyz' } - } - ] - }; + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - const badRequest1 = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - unknownId: 123456 - } - } - ] - }; + it('should return true when placementId is passed', () => { + let bid = { 'params': {} }; + expect(spec.isBidRequestValid(simpleBidRequest)).to.equal(true); + }); - const twoAdSlotsSingleRequest = { - bidderCode: 'improvedigital', - bids: [ - { - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - singleRequest: true, - placementId: 1012544, - } - }, - { - bidId: '4d5e6f', - placementCode: 'placement2', - params: { - placementId: 1012545, - size: { - w: 800, - h: 600 - } + it('should return true when both placementKey and publisherId are passed', () => { + let bid = { 'params': {} }; + expect(spec.isBidRequestValid(simpleSmartTagBidRequest)).to.equal(true); + }); + }); + + describe('buildRequests', () => { + it('should make a well-formed request objects', () => { + const requests = spec.buildRequests([simpleBidRequest]); + expect(requests).to.be.an('array'); + expect(requests.length).to.equal(1); + + const request = requests[0]; + expect(request.method).to.equal(METHOD); + 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)); + 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}`); + expect(params.bid_request.imp).to.deep.equal([ + { + id: '33e9500b21129f', + pid: 1053688, + tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', + banner: {} } - } - ] - }; + ]); + }); - const simpleResponse = { - id: '701903620', - site_id: 191642, - bid: [ - { - price: 1.85185185185185, - lid: 268514, - advid: '5279', - id: '1a2b3c', - sync: [ - 'http://link', - 'http://link2', - 'http://link3' - ], - nurl: 'http://nurl', - h: 300, - pid: 1053687, - crid: '422030', - w: 300, - cid: '99005', - adm: 'document.writeln(\" { + const requests = spec.buildRequests([simpleSmartTagBidRequest]); + const params = JSON.parse(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'); + }); - const zeroPriceResponse = { - id: '701903620', - site_id: 191642, - bid: [ - { - price: 0, - lid: 268514, - advid: '5279', - id: '1a2b3c', - sync: [ - 'http://link', - 'http://link2', - 'http://link3' - ], - nurl: 'http://nurl', - h: 300, - pid: 1053687, - crid: '422030', - w: 300, - cid: '99005', - adm: 'document.writeln(\" { + let bidRequest = Object.assign({}, simpleBidRequest); + const keyValues = { + testKey: [ + 'testValue' + ] + }; + bidRequest.params.keyValues = keyValues; + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(request.data.substring(PARAM_PREFIX.length)); + expect(params.bid_request.imp[0].kvw).to.deep.equal(keyValues); + }); - const multipleResponse = { - id: '701903620', - site_id: 191642, - bid: [ - { - price: 1.85185185185185, - lid: 268514, - advid: '5279', - id: '1a2b3c', - sync: [ - 'http://link', - 'http://link2', - 'http://link3' - ], - nurl: 'http://nurl', - h: 300, - pid: 1053687, - crid: '422030', - w: 300, - cid: '99005', - adm: 'document.writeln(\" { + let bidRequest = Object.assign({}, simpleBidRequest); + const size = { w: 800, - cid: '99005', - adm: 'document.writeln(\" { + 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)); + expect(params.bid_request.imp[0].currency).to.equal('JPY'); + getConfigStub.restore(); + }); + + it('should return 2 requests', () => { + const requests = spec.buildRequests([ + simpleBidRequest, + simpleSmartTagBidRequest + ]); + expect(requests).to.be.an('array'); + expect(requests.length).to.equal(2); + }); + }); + + describe('interpretResponse', () => { + let registerSyncStub; + beforeEach(() => { + registerSyncStub = sinon.stub(userSync, 'registerSync'); + }); + + afterEach(() => { + registerSyncStub.restore(); + }); + const serverResponse = { + 'body': { + 'id': '687a06c541d8d1', + 'site_id': 191642, + 'bid': [ + { + 'isNet': false, + '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=', + 'h': 290, + 'pid': 1053688, + 'sync': [ + 'http://link1', + 'http://link2' + ], + 'crid': '422031', + 'w': 600, + 'cid': '99006', + 'adm': 'document.writeln(\"\\\"\\\"\\/<\\/a>\");document.writeln(\"<\\/improvedigital_ad_output_information>\");' + } ], - nurl: 'http://nurl2', - h: 600, - pid: 1053687, - crid: '422030', - w: 800, - cid: '99005', - adm: 'document.writeln(\"\\\"\\\"\\/<\\/a>\");document.writeln(\"<\\/improvedigital_ad_output_information>\");' + } ], - nurl: 'http://nurl2', - h: 600, - pid: 1053687, - crid: '422030', - w: 800, - cid: '99005' + 'debug': '' } - ], - debug: '' - }; + }; - const simpleResponseNoSync = { - id: '701903620', - site_id: 191642, - bid: [ + let expectedBid = [ { - price: 1.85185185185185, - lid: 268514, - advid: '5279', - id: '1a2b3c', - sync: [], - nurl: 'http://nurl', - h: 300, - pid: 1053687, - crid: '422030', - w: 300, - cid: '99005', - adm: 'document.writeln(\"', + 'adId': '33e9500b21129f', + 'creativeId': '422031', + 'cpm': 1.45888594164456, + 'currency': 'USD', + 'height': 290, + 'netRevenue': false, + 'requestId': '33e9500b21129f', + 'ttl': 300, + 'width': 600 } - ] - }; + ]; - var randomNumber = 9876543210; - beforeEach(() => { - improveDigitalAdapter = new Adapter(); - sandbox = sinon.sandbox.create(); - sandbox.stub( - utils, - 'getUniqueIdentifierStr', - function() { - var retValue = randomNumber.toString(); - randomNumber++; - return retValue; + let expectedTwoBids = [ + expectedBid[0], + { + 'ad': '', + 'adId': '1234', + 'creativeId': '422033', + 'cpm': 1.23, + 'currency': 'USD', + 'height': 400, + 'netRevenue': true, + 'requestId': '1234', + 'ttl': 300, + 'width': 700 } - ); - bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; - $$PREBID_GLOBAL$$._bidsRequested = []; - }); - - afterEach(() => { - sandbox.restore(); - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; - }); + ]; - describe('callBids simpleBidRequest', () => { - beforeEach(() => { - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(simpleBidRequest); - }); - it('should call loadScript with correct parameters', () => { - sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); + it('should return a well-formed bid', () => { + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.deep.equal(expectedBid); }); - }); - describe('callBids simpleSmartTagBidRequest', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(simpleSmartTagBidRequest); - }); - it('should call loadScript with correct parameters', () => { - sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pubid%22%3A1032%2C%22pkey%22%3A%22data_team_test_hb_smoke_test%22%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); + it('should return two bids', () => { + const bids = spec.interpretResponse(serverResponseTwoBids); + expect(bids).to.deep.equal(expectedTwoBids); }); - }); - describe('callBids keyValueBidRequest', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(keyValueBidRequest); + it('should register user syncs', () => { + const bids = spec.interpretResponse(serverResponse); + expect(registerSyncStub.withArgs('image', 'improvedigital', 'http://link1').calledOnce).to.equal(true); + expect(registerSyncStub.withArgs('image', 'improvedigital', 'http://link2').calledOnce).to.equal(true); }); - it('should call loadScript with correct parameters', () => { - sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012546%2C%22kvw%22%3A%7B%22hbkv%22%3A%5B%2201%22%5D%7D%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); - }); - }); - describe('callBids sizeBidRequest', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(sizeBidRequest); - }); - it('should call loadScript with correct parameters', () => { - sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); - }); - }); + it('should set dealId correctly', () => { + let response = JSON.parse(JSON.stringify(serverResponse)); + let bids; - describe('callBids twoAdSlots', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(twoAdSlots); - }); - it('should call loadScript twice with correct parameters', () => { - sinon.assert.calledTwice(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543211%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); - }); - }); + response.body.bid[0].lid = 'xyz'; + bids = spec.interpretResponse(response); + expect(bids[0].dealId).to.not.exist; - describe('callBids threeAdSlots', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(threeAdSlots); - }); - it('should call loadScript thrice with correct parameters', () => { - sinon.assert.calledThrice(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543211%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543212%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%227g8h9i%22%2C%22pid%22%3A1012546%2C%22kvw%22%3A%7B%22hbkv%22%3A%5B%2201%22%5D%7D%2C%22banner%22%3A%7B%7D%7D%5D%7D%7D', null); - }); - }); + response.body.bid[0].lid = 268515; + bids = spec.interpretResponse(response); + expect(bids[0].dealId).to.equal(268515); - describe('callBids bad request 1', () => { - beforeEach(() => { - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(badRequest1); - }); - it('should not call loadScript', () => { - sinon.assert.notCalled(adloader.loadScript); + response.body.bid[0].lid = { + 1: 268515 + }; + bids = spec.interpretResponse(response); + expect(bids[0].dealId).to.equal(268515); }); - }); - describe('callBids twoAdSlotsSingleRequest', () => { - beforeEach(() => { - randomNumber = 9876543210; - sandbox.stub( - adloader, - 'loadScript' - ); - improveDigitalAdapter.callBids(twoAdSlotsSingleRequest); - }); - it('should call loadScript twice with correct parameters', () => { - sinon.assert.calledOnce(adloader.loadScript); - sinon.assert.calledWith(adloader.loadScript, 'http://ad.360yield.com/hb?jsonp=%7B%22bid_request%22%3A%7B%22id%22%3A%229876543210%22%2C%22callback%22%3A%22$$PREBID_GLOBAL$$.improveDigitalResponse%22%2C%22secure%22%3A0%2C%22version%22%3A%22' + improveDigitalAdapter.LIB_VERSION + '-' + improveDigitalAdapter.idClient.CONSTANTS.CLIENT_VERSION + '%22%2C%22imp%22%3A%5B%7B%22id%22%3A%221a2b3c%22%2C%22pid%22%3A1012544%2C%22banner%22%3A%7B%7D%7D%2C%7B%22id%22%3A%224d5e6f%22%2C%22pid%22%3A1012545%2C%22banner%22%3A%7B%22w%22%3A800%2C%22h%22%3A600%7D%7D%5D%7D%7D', null); - }); - }); - - describe('improveDigitalResponse no response', () => { - beforeEach(() => { - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); - improveDigitalAdapter.callBids(simpleBidRequest); - $$PREBID_GLOBAL$$.improveDigitalResponse([]); + it('should set currency', () => { + let response = JSON.parse(JSON.stringify(serverResponse)); + response.body.bid[0].currency = 'eur'; + const bids = spec.interpretResponse(response); + expect(bids[0].currency).to.equal('EUR'); }); - it('should not call bidmanager.addBidResponse', () => { - sinon.assert.notCalled(bidmanager.addBidResponse); - }); - }); - describe('improveDigitalResponse simpleResponse', () => { - beforeEach(() => { - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); - improveDigitalAdapter.callBids(simpleBidRequest); - $$PREBID_GLOBAL$$.improveDigitalResponse(simpleResponse); - }); - it('should call bidmanager.addBidResponse once with correct parameters', () => { - sinon.assert.calledOnce(bidmanager.addBidResponse); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 300, height: 300, statusMessage: 'Bid available', ad: '', cpm: 1.85185185185185, adId: '1a2b3c'})); - }); - }); + it('should return empty array for bad response or no price', () => { + let response = JSON.parse(JSON.stringify(serverResponse)); + let bids; - describe('improveDigitalResponse zero bid', () => { - beforeEach(() => { - randomNumber = 1111111111; - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); - improveDigitalAdapter.callBids(simpleBidRequest); - $$PREBID_GLOBAL$$.improveDigitalResponse(zeroPriceResponse); - }); - it('should call bidmanager.addBidResponse once with correct parameters', () => { - sinon.assert.calledOnce(bidmanager.addBidResponse); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, statusMessage: 'Bid returned empty or error response', adId: '1a2b3c'})); - }); - }); + // Price missing or 0 + response.body.bid[0].price = 0; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); + delete response.body.bid[0].price; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); + response.body.bid[0].price = null; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); - describe('improveDigitalResponse multipleResponseWithOneNoBid', () => { - beforeEach(() => { - randomNumber = 1111111111; - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(twoAdSlots); - improveDigitalAdapter.callBids(twoAdSlots); - $$PREBID_GLOBAL$$.improveDigitalResponse(multipleResponseWithOneNoBid); - }); - it('should call bidmanager.addBidResponse once with correct parameters', () => { - sinon.assert.calledTwice(bidmanager.addBidResponse); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 300, height: 300, adId: '1a2b3c', statusMessage: 'Bid available', ad: '', cpm: 1.85185185185185})); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement2', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, adId: '4d5e6f', statusMessage: 'Bid returned empty or error response'})); - }); - }); + // errorCode present + response = JSON.parse(JSON.stringify(serverResponse)); + response.body.bid[0].errorCode = undefined; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); - describe('improveDigitalResponse multipleInvalidResponses', () => { - beforeEach(() => { - randomNumber = 1111111111; - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(twoAdSlots); - improveDigitalAdapter.callBids(twoAdSlots); - $$PREBID_GLOBAL$$.improveDigitalResponse(multipleInvalidResponses); + // Adm missing or bad + response = JSON.parse(JSON.stringify(serverResponse)); + delete response.body.bid[0].adm; + bids = spec.interpretResponse(response); + expect(bids).to.deep.equal([]); + 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 call bidmanager.addBidResponse twice both with invalid', () => { - sinon.assert.calledTwice(bidmanager.addBidResponse); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, adId: '1a2b3c', statusMessage: 'Bid returned empty or error response'})); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement2', sinon.match({bidderCode: 'improvedigital', width: 0, height: 0, adId: '4d5e6f', statusMessage: 'Bid returned empty or error response'})); - }); - }); - describe('improveDigitalResponse simpleResponseNoSync', () => { - beforeEach(() => { - sandbox.stub( - bidmanager, - 'addBidResponse' - ); - $$PREBID_GLOBAL$$._bidsRequested.push(simpleBidRequest); - improveDigitalAdapter.callBids(simpleBidRequest); - $$PREBID_GLOBAL$$.improveDigitalResponse(simpleResponseNoSync); - }); - it('should call bidmanager.addBidResponse once with correct parameters', () => { - sinon.assert.calledOnce(bidmanager.addBidResponse); - sinon.assert.calledWith(bidmanager.addBidResponse, 'placement1', sinon.match({bidderCode: 'improvedigital', width: 300, height: 300, statusMessage: 'Bid available', ad: '', cpm: 1.85185185185185, adId: '1a2b3c'})); + it('should set netRevenue', () => { + let response = JSON.parse(JSON.stringify(serverResponse)); + response.body.bid[0].isNet = true; + const bids = spec.interpretResponse(response); + expect(bids[0].netRevenue).to.equal(true); }); }); }); diff --git a/test/spec/modules/indexExchangeBidAdapter_request_spec.js b/test/spec/modules/indexExchangeBidAdapter_request_spec.js deleted file mode 100644 index 787c6bafde4..00000000000 --- a/test/spec/modules/indexExchangeBidAdapter_request_spec.js +++ /dev/null @@ -1,528 +0,0 @@ -import Adapter from 'modules/indexExchangeBidAdapter'; -import bidManager from 'src/bidmanager'; -import adLoader from 'src/adloader'; -import * as url from 'src/url'; - -var assert = require('chai').assert; -var IndexUtils = require('../../helpers/index_adapter_utils.js'); -var HeaderTagRequest = '/cygnus'; -var SlotThreshold = 20; -var ADAPTER_CODE = 'indexExchange'; - -window.pbjs = window.pbjs || {}; - -describe('indexExchange adapter - Request', function () { - let adapter; - let sandbox; - - beforeEach(function() { - window._IndexRequestData = {}; - _IndexRequestData.impIDToSlotID = {}; - _IndexRequestData.reqOptions = {}; - _IndexRequestData.targetIDToResp = {}; - window.cygnus_index_args = {}; - - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - sandbox.stub(adLoader, 'loadScript'); - sandbox.stub(bidManager, 'addBidResponse'); - }); - - afterEach(function() { - sandbox.restore(); - }); - - it('test_prebid_indexAdapter_parameter_x3: prebid sends AS request -> x3 parameter does not exist in the request', function () { - var configuredBids = IndexUtils.createBidSlots(); - adapter.callBids({ bids: configuredBids }); - - assert.notInclude(adLoader.loadScript.firstCall.args[0], 'x3=', 'x3 parameter is not in AS request'); - }); - - it('test_prebid_indexAdapter_request_1_1: single slot with single size -> single request object for the slot', function () { - var configuredBids = IndexUtils.createBidSlots(); - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.isString(requestJSON.r.id, 'ID is string'); - }); - - it('test_prebid_indexAdapter_request_1_1: single slot with single size -> single request object for the slot', function () { - var configuredBids = IndexUtils.createBidSlots(); - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - }); - - it('test_prebid_indexAdapter_request_1_2: single slot with unsupported single size -> indexExchange does not participate in auction', function () { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.unsupportedSizes[0] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isFalse(adLoader.loadScript.called, 'no request made to AS'); - - var adapterResponse = {}; - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - }; - assert.deepEqual(Object.keys(adapterResponse), [IndexUtils.DefaultPlacementCodePrefix], 'bid response from placement code that is configured'); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix].length, 1, 'one response back returned for placement ' + IndexUtils.DefaultPlacementCodePrefix); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].bidderCode, ADAPTER_CODE, "bidder code match with adapter's name"); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].statusMessage, 'Bid returned empty or error response', 'pass on bid message'); - }); - - it('test_prebid_indexAdapter_request_2_1: single slot with all supported multiple sizes -> multiple request objects for the slot', function () { - var configuredBids = IndexUtils.createBidSlots(1, 5); - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - }); - - it('test_prebid_indexAdapter_request_2_2: single slot with all unsupported multiple sizes -> no request objects for the slot', function () { - var isSetExpectedBidsCountCalled = false; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.unsupportedSizes[0], IndexUtils.unsupportedSizes[1], IndexUtils.unsupportedSizes[2] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isFalse(adLoader.loadScript.called, 'no request made to AS'); - }); - - it('test_prebid_indexAdapter_request_2_3: single slot with supported, unsupportrd, supported sizes -> only the supported size request objects for the slot', function () { - var unsupportedSize = IndexUtils.unsupportedSizes[0]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0], unsupportedSize, IndexUtils.supportedSizes[1] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, unsupportedSize, 'configured bid not in impression obj size width is' + JSON.stringify(unsupportedSize)); - }); - - it('test_prebid_indexAdapter_request_2_4: single slot with unsupported, supportrd, unsupported sizes -> only the supported size request objects for the slot', function () { - var unsupportedSize1 = IndexUtils.unsupportedSizes[0]; - var unsupportedSize2 = IndexUtils.unsupportedSizes[1]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ unsupportedSize1, IndexUtils.supportedSizes[1], unsupportedSize2 ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.equal(sidMatched.unmatched.configured.length, 2, '2 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, unsupportedSize1, 'configured bid not in impression obj size width is' + JSON.stringify(unsupportedSize1)); - assert.equal(sidMatched.unmatched.configured[1].size, unsupportedSize2, 'configured bid not in impression obj size width is' + JSON.stringify(unsupportedSize2)); - }); - - it('test_prebid_indexAdapter_request_3: multiple slots with single size below allowed slot threshold -> request for all the slots', function () { - var configuredBids = IndexUtils.createBidSlots(10); - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - }); - - it('test_prebid_indexAdapter_request_4: multiple slots with single size at exact allowed slot threshold -> request for all the slots', function () { - var configuredBids = IndexUtils.createBidSlots(SlotThreshold); - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - }); - - it('test_prebid_indexAdapter_request_5: multiple slots with single size exceed allowed slot threshold -> request for all the slots', function () { - var requestSlotNumber = SlotThreshold + 1; - var configuredBids = IndexUtils.createBidSlots(requestSlotNumber); - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - }); - - it('test_prebid_indexAdapter_request_6: threshold valid + non valid which exceeds threshold -> 1 Ad Server request with supported sizes only', function () { - var unsupportedSizeCount = 1; - var requestSlotNumber = SlotThreshold; - var configuredBids = IndexUtils.createBidSlots(requestSlotNumber); - // add additional unsupported sized slot - var invalidSlotPlacement = IndexUtils.DefaultPlacementCodePrefix + 'invalid'; - var invalidSlotID = 'slot-invalid'; - configuredBids.push(IndexUtils.createBidSlot(invalidSlotPlacement, invalidSlotID, [ IndexUtils.unsupportedSizes[0] ])); - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.configured.length, unsupportedSizeCount, unsupportedSizeCount + ' of configured bids is missing in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].placementCode, invalidSlotPlacement, "missing slot's placement code is " + invalidSlotPlacement); - assert.equal(sidMatched.unmatched.configured[0].params.id, invalidSlotID, "missing slot's slotID is " + invalidSlotID); - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - }); - - it('test_prebid_indexAdapter_request_7: multiple sizes with slots that exceeds max threshold requests -> 1 Ad Server request with supported sizes only', function () { - var requestSlotNumber = SlotThreshold; - var requestSizeNumber = 2; - var configuredBids = IndexUtils.createBidSlots(requestSlotNumber, requestSizeNumber); - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - - assert.equal(sidMatched.matched.length, requestSlotNumber * requestSizeNumber, 'All slots each with multiple sizes are in AS request'); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - }); - - it('test_prebid_indexAdapter_request_sizeID_1: 1 prebid size slot, 1 index slot with size -> one slot in AS request 1 no size ID', function () { - var slotID = 52; - var slotSizes = IndexUtils.supportedSizes[0]; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, slotID, [ slotSizes ], { slotSize: slotSizes }) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - assert.equal(impressionObj.length, 1, '1 slot is made in the request'); - assert.equal(impressionObj[0].banner.w, slotSizes[0], 'the width made in the request matches with request: ' + slotSizes[0]); - assert.equal(impressionObj[0].banner.h, slotSizes[1], 'the height made in the request matches with request: ' + slotSizes[1]); - assert.equal(impressionObj[0].ext.sid, slotID, 'slotID in the request matches with configuration: ' + slotID); - assert.equal(impressionObj[0].ext.siteID, IndexUtils.DefaultSiteID, 'siteID in the request matches with request: ' + IndexUtils.DefaultSiteID); - }); - - it('test_prebid_indexAdapter_request_sizeID_2: multiple prebid size slot, 1 index slot with size -> one slot in AS request 1 no size ID', function () { - var slotID = 52; - var slotSizes = IndexUtils.supportedSizes[0]; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, slotID, [ slotSizes, IndexUtils.supportedSizes[1] ], { slotSize: slotSizes }) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - assert.equal(impressionObj.length, 1, '1 slot is made in the request'); - assert.equal(impressionObj[0].banner.w, slotSizes[0], 'the width made in the request matches with request: ' + slotSizes[0]); - assert.equal(impressionObj[0].banner.h, slotSizes[1], 'the height made in the request matches with request: ' + slotSizes[1]); - assert.equal(impressionObj[0].ext.sid, slotID, 'slotID in the request matches with configuration: ' + slotID); - assert.equal(impressionObj[0].ext.siteID, IndexUtils.DefaultSiteID, 'siteID in the request matches with request: ' + IndexUtils.DefaultSiteID); - }); - - it('test_prebid_indexAdapter_request_sizeID_3: multiple prebid size slot, index slots with size for all prebid slots -> all size in AS request, no size ID', function () { - var slotID_1 = 52; - var slotID_2 = 53; - var slotSizes_1 = IndexUtils.supportedSizes[0]; - var slotSizes_2 = IndexUtils.supportedSizes[1]; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, slotID_1, [ slotSizes_1, slotSizes_2 ], { slotSize: slotSizes_1 }), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, slotID_2, [ slotSizes_1, slotSizes_2 ], { slotSize: slotSizes_2 }) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - assert.equal(impressionObj.length, 2, '2 slot is made in the request'); - assert.equal(impressionObj[0].banner.w, slotSizes_1[0], 'the width made in the request matches with request: ' + slotSizes_1[0]); - assert.equal(impressionObj[0].banner.h, slotSizes_1[1], 'the height made in the request matches with request: ' + slotSizes_1[1]); - assert.equal(impressionObj[0].ext.sid, slotID_1, 'slotID in the request matches with configuration: ' + slotID_1); - assert.equal(impressionObj[0].ext.siteID, IndexUtils.DefaultSiteID, 'siteID in the request matches with request: ' + IndexUtils.DefaultSiteID); - - assert.equal(impressionObj[1].banner.w, slotSizes_2[0], 'the width made in the request matches with request: ' + slotSizes_2[0]); - assert.equal(impressionObj[1].banner.h, slotSizes_2[1], 'the height made in the request matches with request: ' + slotSizes_2[1]); - assert.equal(impressionObj[1].ext.sid, slotID_2, 'slotID in the request matches with configuration: ' + slotID_2); - assert.equal(impressionObj[1].ext.siteID, IndexUtils.DefaultSiteID, 'siteID in the request matches with request: ' + IndexUtils.DefaultSiteID); - }); - - it('test_prebid_indexAdapter_request_sizeID_4: multiple prebid size slot, 1 index slot but size not in prebid defined size git -> no AS requset', function () { - var slotID = 52; - var slotSizes = IndexUtils.unsupportedSizes[0]; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, slotID, [ IndexUtils.supportedSizes[0] ], { slotSize: slotSizes }) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isFalse(adLoader.loadScript.called, 'no request made to AS'); - }); - - it('test_prebid_indexAdapter_request_sizeID_5: multiple prebid size slot, 1 index slot but size not defined in slot -> no AS requset', function () { - var slotID = 52; - var slotSizes = IndexUtils.supportedSizes[1]; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, slotID, [ IndexUtils.supportedSizes[0] ], { slotSize: slotSizes }) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isFalse(adLoader.loadScript.called, 'no request made to AS'); - }); - - it('test_prebid_indexAdapter_request_different_type_adUnits: both display and video slots -> 2 Ad Server requests, 1 for display and 1 for video', function() { - var videoConfig = { - 'siteID': 6, - 'playerType': 'HTML5', - 'protocols': ['VAST2', 'VAST3'], - 'maxduration': 15 - } - var videoWidth = 640; - var videoHeight = 480; - var configuredBids = IndexUtils.createBidSlots(2); - configuredBids[1].params.video = Object.assign({}, videoConfig); - configuredBids[1].mediaType = 'video'; - configuredBids[1].sizes[0] = videoWidth; - configuredBids[1].sizes[1] = videoHeight; - - adapter.callBids({ bids: configuredBids }); - - sinon.assert.calledTwice(adLoader.loadScript); - - // Check request for display ads - assert.include(adLoader.loadScript.secondCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.secondCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = [IndexUtils.expandSizes(configuredBids[0])]; - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.isString(requestJSON.r.id, 'ID is string'); - - // Check request for video ads - let cygnusRequestUrl = url.parse(encodeURIComponent(adLoader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].ext.siteID).to.equal(videoConfig.siteID); - expect(cygnusRequestUrl.search.r.imp[0].video.maxduration).to.equal(videoConfig.maxduration); - expect(cygnusRequestUrl.search.r.imp[0].video.w).to.equal(videoWidth); - expect(cygnusRequestUrl.search.r.imp[0].video.h).to.equal(videoHeight); - }); -}); diff --git a/test/spec/modules/indexExchangeBidAdapter_response_spec.js b/test/spec/modules/indexExchangeBidAdapter_response_spec.js deleted file mode 100644 index 817244a2c68..00000000000 --- a/test/spec/modules/indexExchangeBidAdapter_response_spec.js +++ /dev/null @@ -1,1170 +0,0 @@ -import Adapter from 'modules/indexExchangeBidAdapter'; -import bidManager from 'src/bidmanager'; -import adLoader from 'src/adloader'; - -var assert = require('chai').assert; -var IndexUtils = require('../../helpers/index_adapter_utils.js'); -var HeaderTagRequest = '/cygnus'; -var SlotThreshold = 20; -var ADAPTER_CODE = 'indexExchange'; -var DefaultValue = { - dealID: 'IXDeal' -}; -window.pbjs = window.pbjs || {}; -var ResponseStatus = { - noBid: 'Bid returned empty or error response' -}; - -describe('indexExchange adapter - Response', function () { - let adapter; - let sandbox; - - beforeEach(function() { - window._IndexRequestData = {}; - _IndexRequestData.impIDToSlotID = {}; - _IndexRequestData.reqOptions = {}; - _IndexRequestData.targetIDToResp = {}; - window.cygnus_index_args = {}; - - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - sandbox.stub(bidManager, 'addBidResponse'); - sandbox.stub(adLoader, 'loadScript'); - }); - - afterEach(function() { - sandbox.restore(); - }); - - it('test_prebid_indexAdapter_response_1_1: response for single slot with single size -> bid fetched into prebid', function () { - var configuredBids = IndexUtils.createBidSlots(1, 1); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON); - cygnus_index_parse_res(asResponse); - - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - assert.equal(pair.prebid.length, 1, 'Only one bid is ferched into prebid'); - assert.equal(pair.prebid[0].siteID, pair.expected[0].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[0].siteID); - assert.equal(pair.prebid[0].bidderCode, pair.expected[0].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[0].bidderCode); - assert.equal(pair.prebid[0].width, pair.expected[0].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[0].width); - assert.equal(pair.prebid[0].height, pair.expected[0].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[0].height); - assert.equal(pair.prebid[0].ad, pair.expected[0].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[0].ad); - assert.equal(pair.prebid[0].cpm, pair.expected[0].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[0].cpm); - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_1_2: pass on bid for single slot with single size -> bid fetched into prebid', function () { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot1', [ IndexUtils.supportedSizes[0] ]), - ]; - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, [ [ true ] ]); - cygnus_index_parse_res(asResponse); - - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - assert.equal(prebidResponsePair.matched.length, 0, 'No bids are added to prebid'); - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 1, 'no Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_2_1: response for single slot with multiple sizes -> all bids fetched into prebid', function () { - var configuredBids = IndexUtils.createBidSlots(1, 3); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON); - cygnus_index_parse_res(asResponse); - - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse, [ [1000, 3000, 2000] ]); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - - assert.equal(pair.prebid.length, 3, 'all bids are fetched into prebid'); - for (var j = 0; j < pair.prebid.length; j++) { - assert.equal(pair.prebid[j].siteID, pair.expected[j].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[j].siteID); - assert.equal(pair.prebid[j].bidderCode, pair.expected[j].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[j].bidderCode); - assert.equal(pair.prebid[j].width, pair.expected[j].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[j].width); - assert.equal(pair.prebid[j].height, pair.expected[j].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[j].height); - assert.equal(pair.prebid[j].ad, pair.expected[j].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[j].ad); - assert.equal(pair.prebid[j].cpm, pair.expected[j].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[j].cpm); - } - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_2_2: pass on bid on some sizes for single slot with multiple sizes -> highest bid fetched into prebid', function () { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot1', [ IndexUtils.supportedSizes[0], IndexUtils.supportedSizes[1] ]), - ]; - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - - // pass on bid on second size - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, [ [ false, true ] ]); - cygnus_index_parse_res(asResponse); - - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - assert.equal(prebidResponsePair.matched.length, 1, 'one slot is added to prebid'); - var pair = prebidResponsePair.matched[0]; - assert.equal(pair.prebid[0].siteID, pair.expected[0].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[0].siteID); - assert.equal(pair.prebid[0].bidderCode, pair.expected[0].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[0].bidderCode); - assert.equal(pair.prebid[0].width, pair.expected[0].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[0].width); - assert.equal(pair.prebid[0].height, pair.expected[0].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[0].height); - assert.equal(pair.prebid[0].ad, pair.expected[0].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[0].ad); - assert.equal(pair.prebid[0].cpm, pair.expected[0].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[0].cpm); - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_2_3: pass on bid on all sizes for a single slot -> no bids fetched into prebid', function () { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot1', [ IndexUtils.supportedSizes[0], IndexUtils.supportedSizes[1] ]), - ]; - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - - // pass on bid on all bids - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, [ [ true, true ] ]); - cygnus_index_parse_res(asResponse); - - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - assert.equal(prebidResponsePair.matched.length, 0, 'no bids fetched into prebid'); - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid[0][0].statusMessage, ResponseStatus.noBid, 'Bid response status is set to ' + ResponseStatus.noBid); - }); - - it('test_prebid_indexAdapter_response_3_1: response for multiple slots request with single size for each slots -> all response for all adunit fetched into prebid', function () { - var configuredBids = IndexUtils.createBidSlots(20); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON); - cygnus_index_parse_res(asResponse); - - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse, [ [1000, 3000, 2000] ]); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - - assert.equal(pair.prebid.length, 1, 'all bids are fetched into prebid'); - assert.equal(pair.prebid[0].siteID, pair.expected[0].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[0].siteID); - assert.equal(pair.prebid[0].bidderCode, pair.expected[0].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[0].bidderCode); - assert.equal(pair.prebid[0].width, pair.expected[0].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[0].width); - assert.equal(pair.prebid[0].height, pair.expected[0].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[0].height); - assert.equal(pair.prebid[0].ad, pair.expected[0].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[0].ad); - assert.equal(pair.prebid[0].cpm, pair.expected[0].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[0].cpm); - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_3_2: some slots response returned -> returned bids fetched into prebid ', function () { - var configuredBids = IndexUtils.createBidSlots(2); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - - var passOnBid = [ - [ false ], // bids back on first slot - [ true ], // pass on bid on second slot - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, passOnBid); - cygnus_index_parse_res(asResponse); - - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse, [ [1000, 3000, 2000] ]); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - assert.equal(prebidResponsePair.matched.length, 1, '1 bid from ad server is fetched into prebid'); - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - - assert.equal(pair.prebid.length, 1, 'all bids are fetched into prebid'); - assert.equal(pair.prebid[0].siteID, pair.expected[0].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[0].siteID); - assert.equal(pair.prebid[0].bidderCode, pair.expected[0].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[0].bidderCode); - assert.equal(pair.prebid[0].width, pair.expected[0].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[0].width); - assert.equal(pair.prebid[0].height, pair.expected[0].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[0].height); - assert.equal(pair.prebid[0].ad, pair.expected[0].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[0].ad); - assert.equal(pair.prebid[0].cpm, pair.expected[0].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[0].cpm); - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 1, 'One slot passed on bid from Ad Server'); - }); - - it('test_prebid_indexAdapter_response_3_3: response for multiple slots with no response returned -> no bid fetched into prebid', function () { - var configuredBids = IndexUtils.createBidSlots(2); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - - var passOnBid = [ - [ true ], // pass on bid on the first slot - [ true ], // pass on bid on the second slot - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, passOnBid); - cygnus_index_parse_res(asResponse); - - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse, [ [1000, 3000, 2000] ]); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - assert.equal(prebidResponsePair.matched.length, 0, 'no bids from ad server is fetched into prebid'); - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 2, 'two slots passed on bid from Ad Server'); - }); - - it("test_prebid_indexAdapter_refreshSlot_1: slot refreshes multiple times with different bids on refresh with same price -> response to prebid use correct AS response's creative", function () { - var configuredBids = IndexUtils.createBidSlots(1, 1); - - var refreshSetup = [ {price: 1000, request: 'request-1'}, {price: 1000, request: 'request-2'} ]; - for (var i = 0; i < refreshSetup.length; i++) { - var requestParams = refreshSetup[i]; - - adapter.callBids({ bids: configuredBids }); - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - - // first ix call - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, [ [requestParams.price] ], requestParams.request); - cygnus_index_parse_res(asResponse); - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var j = 0; j < prebidResponsePair.matched.length; j++) { - var pair = prebidResponsePair.matched[j]; - - assert.equal(pair.prebid.length, 1, 'all bids are fetched into prebid'); - for (var k = 0; k < pair.prebid.length; k++) { - assert.equal(pair.prebid[k].siteID, pair.expected[k].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[k].siteID); - assert.equal(pair.prebid[k].bidderCode, pair.expected[k].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[k].bidderCode); - assert.equal(pair.prebid[k].width, pair.expected[k].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[k].width); - assert.equal(pair.prebid[k].height, pair.expected[k].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[k].height); - assert.equal(pair.prebid[k].ad, pair.expected[k].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[k].ad); - assert.equal(pair.prebid[k].cpm, pair.expected[k].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[k].cpm); - } - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - - bidManager.addBidResponse.reset(); - adapterResponse = {}; // initialize adapterReaponse for refresh test - } - }); - - it("test_prebid_indexAdapter_refreshSlot_2: slot refreshes multiple times with different bids on refresh with different price, but first bid is higher -> response to prebid use correct AS response's creative", function () { - var configuredBids = IndexUtils.createBidSlots(1, 1); - - var refreshSetup = [ {price: 8000, request: 'request-1'}, {price: 1000, request: 'request-2'} ]; - for (var i = 0; i < refreshSetup.length; i++) { - var requestParams = refreshSetup[i]; - - adapter.callBids({ bids: configuredBids }); - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - - // first ix call - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, [ [requestParams.price] ], requestParams.request); - cygnus_index_parse_res(asResponse); - - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var j = 0; j < prebidResponsePair.matched.length; j++) { - var pair = prebidResponsePair.matched[j]; - - assert.equal(pair.prebid.length, 1, 'all bids are fetched into prebid'); - for (var k = 0; k < pair.prebid.length; k++) { - assert.equal(pair.prebid[k].siteID, pair.expected[k].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[k].siteID); - assert.equal(pair.prebid[k].bidderCode, pair.expected[k].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[k].bidderCode); - assert.equal(pair.prebid[k].width, pair.expected[k].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[k].width); - assert.equal(pair.prebid[k].height, pair.expected[k].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[k].height); - assert.equal(pair.prebid[k].ad, pair.expected[k].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[k].ad); - assert.equal(pair.prebid[k].cpm, pair.expected[k].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[k].cpm); - } - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - bidManager.addBidResponse.reset(); - adapterResponse = {}; // initialize adapterReaponse for refresh test - } - }); - - it("test_prebid_indexAdapter_refreshSlot_3: slot refreshes multiple times with different bids on refresh with different price, but first bid is lower -> response to prebid use correct AS response's creative", function () { - var configuredBids = IndexUtils.createBidSlots(1, 1); - - var refreshSetup = [ {price: 1000, request: 'request-1'}, {price: 8000, request: 'request-2'} ]; - for (var i = 0; i < refreshSetup.length; i++) { - var requestParams = refreshSetup[i]; - - adapter.callBids({ bids: configuredBids }); - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - - // first ix call - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, [ [requestParams.price] ], requestParams.request); - cygnus_index_parse_res(asResponse); - - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var j = 0; j < prebidResponsePair.matched.length; j++) { - var pair = prebidResponsePair.matched[j]; - - assert.equal(pair.prebid.length, 1, 'all bids are fetched into prebid'); - for (var k = 0; k < pair.prebid.length; k++) { - assert.equal(pair.prebid[k].siteID, pair.expected[k].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[k].siteID); - assert.equal(pair.prebid[k].bidderCode, pair.expected[k].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[k].bidderCode); - assert.equal(pair.prebid[k].width, pair.expected[k].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[k].width); - assert.equal(pair.prebid[k].height, pair.expected[k].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[k].height); - assert.equal(pair.prebid[k].ad, pair.expected[k].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[k].ad); - assert.equal(pair.prebid[k].cpm, pair.expected[k].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[k].cpm); - } - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - bidManager.addBidResponse.reset(); - adapterResponse = {}; // initialize adapterReaponse for refresh test - } - }); - - it('test_prebid_indexAdapter_refreshSlot_4: got no response the second time -> no bids fetched into prebid', function () { - var configuredBids = IndexUtils.createBidSlots(1, 1); - - var refreshSetup = [ { price: 1000, request: 'request-1', passOnBid: false}, { price: 1000, request: 'request-2', passOnBid: true} ]; - for (var i = 0; i < refreshSetup.length; i++) { - var requestParams = refreshSetup[i]; - - adapter.callBids({ bids: configuredBids }); - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - - // first ix call - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, [ [requestParams.price] ], requestParams.request, [ [ requestParams.passOnBid ] ]); - cygnus_index_parse_res(asResponse); - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var j = 0; j < prebidResponsePair.matched.length; j++) { - var pair = prebidResponsePair.matched[j]; - - assert.equal(pair.prebid.length, 1, 'all bids are fetched into prebid'); - for (var k = 0; k < pair.prebid.length; k++) { - assert.equal(pair.prebid[k].siteID, pair.expected[k].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[k].siteID); - assert.equal(pair.prebid[k].bidderCode, pair.expected[k].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[k].bidderCode); - assert.equal(pair.prebid[k].width, pair.expected[k].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[k].width); - assert.equal(pair.prebid[k].height, pair.expected[k].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[k].height); - assert.equal(pair.prebid[k].ad, pair.expected[k].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[k].ad); - assert.equal(pair.prebid[k].cpm, pair.expected[k].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[k].cpm); - } - } - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - if (requestParams.passOnBid) { - assert.equal(prebidResponsePair.unmatched.prebid.length, 1, '1 Adapter response is missing'); - } else { - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - } - - bidManager.addBidResponse.reset(); - adapterResponse = {}; // initialize adapterReaponse for refresh test - } - }); - - it('test_prebid_indexAdapter_refreshSlot_5: unsupported slots refresh -> no ad server request, no bids fetched into prebid', function () { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.unsupportedSizes[0] ]) - ]; - - var refreshSetup = [ { request: 'request-1' }, { request: 'request-2' } ]; - for (var i = 0; i < refreshSetup.length; i++) { - var requestParams = refreshSetup[i]; - - adapter.callBids({ bids: configuredBids }); - assert.isFalse(adLoader.loadScript.called, 'no ad server request for ' + requestParams.request) - } - }); - - it('test_prebid_indexAdapter_response_deal_1_1: response for single slot with single size contains alpha deal -> bid fetched into prebid', function () { - var configuredBids = IndexUtils.createBidSlots(1, 1); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var optionalResponseParam = [ - [ - { ext: { dealid: 'ixDeal' } } // first slot first size - ] - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam); - cygnus_index_parse_res(asResponse); - - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - - assert.equal(pair.prebid[i].siteID, pair.expected[i].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[i].siteID); - assert.equal(pair.prebid[i].bidderCode, pair.expected[i].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[i].bidderCode); - assert.equal(pair.prebid[i].width, pair.expected[i].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[i].width); - assert.equal(pair.prebid[i].height, pair.expected[i].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[i].height); - assert.equal(pair.prebid[i].ad, pair.expected[i].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[i].ad); - assert.equal(pair.prebid[i].cpm, pair.expected[i].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[i].cpm); - assert.equal(pair.prebid[i].dealId, pair.expected[i].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is set to ' + pair.expected[i].dealId); - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_deal_1_2: response for single slot with single size contains numeric deal -> bid fetched into prebid', function () { - var configuredBids = IndexUtils.createBidSlots(1, 1); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var optionalResponseParam = [ - [ - { ext: { dealid: '239' } } // first slot first size - ] - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam); - cygnus_index_parse_res(asResponse); - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - - assert.equal(pair.prebid[i].siteID, pair.expected[i].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[i].siteID); - assert.equal(pair.prebid[i].bidderCode, pair.expected[i].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[i].bidderCode); - assert.equal(pair.prebid[i].width, pair.expected[i].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[i].width); - assert.equal(pair.prebid[i].height, pair.expected[i].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[i].height); - assert.equal(pair.prebid[i].ad, pair.expected[i].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[i].ad); - assert.equal(pair.prebid[i].cpm, pair.expected[i].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[i].cpm); - assert.equal(pair.prebid[i].dealId, pair.expected[i].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is set to ' + pair.expected[i].dealId); - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_deal_1_3: response for single slot with single size contains alpha-numeric deal starting with numeric -> bid fetched into prebid', function () { - var configuredBids = IndexUtils.createBidSlots(1, 1); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var optionalResponseParam = [ - [ - { ext: { dealid: '1234Deal' } } // first slot first size - ] - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam); - cygnus_index_parse_res(asResponse); - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - - assert.equal(pair.prebid[i].siteID, pair.expected[i].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[i].siteID); - assert.equal(pair.prebid[i].bidderCode, pair.expected[i].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[i].bidderCode); - assert.equal(pair.prebid[i].width, pair.expected[i].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[i].width); - assert.equal(pair.prebid[i].height, pair.expected[i].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[i].height); - assert.equal(pair.prebid[i].ad, pair.expected[i].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[i].ad); - assert.equal(pair.prebid[i].cpm, pair.expected[i].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[i].cpm); - assert.equal(pair.prebid[i].dealId, pair.expected[i].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is set to ' + pair.expected[i].dealId); - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_deal_1_4: response for single slot with single size contains alpha-numeric deal starting with non-numeric -> bid fetched into prebid ', function () { - var configuredBids = IndexUtils.createBidSlots(1, 1); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var optionalResponseParam = [ - [ - { ext: { dealid: 'deal1234' } } // first slot first size - ] - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam); // Alpha numeric starting with non-numeric - cygnus_index_parse_res(asResponse); - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - - assert.equal(pair.prebid[i].siteID, pair.expected[i].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[i].siteID); - assert.equal(pair.prebid[i].bidderCode, pair.expected[i].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[i].bidderCode); - assert.equal(pair.prebid[i].width, pair.expected[i].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[i].width); - assert.equal(pair.prebid[i].height, pair.expected[i].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[i].height); - assert.equal(pair.prebid[i].ad, pair.expected[i].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[i].ad); - assert.equal(pair.prebid[i].cpm, pair.expected[i].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[i].cpm); - assert.equal(pair.prebid[i].dealId, pair.expected[i].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is set to ' + pair.expected[i].dealId); - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_deal_2_1: response for single slot with multi size, all deal bids returned -> all bid fetched into prebid as deal bid', function () { - var sizeCount = 2; - var configuredBids = IndexUtils.createBidSlots(1, sizeCount); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var optionalResponseParam = [ - [ - { ext: { deal: 'deal1', dealid: 'ixDealID1', dealname: 'deal name 1' } }, // first slot first size - { ext: { deal: 'deal2', dealid: 'ixDealID2', dealname: 'deal name 2' } }, // first slot second size - ] - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam); - cygnus_index_parse_res(asResponse); - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - - for (var j = 0; j < pair.expected.length; j++) { - assert.equal(pair.prebid[j].siteID, pair.expected[j].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[i].siteID); - assert.equal(pair.prebid[j].bidderCode, pair.expected[j].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[i].bidderCode); - assert.equal(pair.prebid[j].width, pair.expected[j].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[i].width); - assert.equal(pair.prebid[j].height, pair.expected[j].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[i].height); - assert.equal(pair.prebid[j].ad, pair.expected[j].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[i].ad); - assert.equal(pair.prebid[j].cpm, pair.expected[j].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[i].cpm); - assert.equal(pair.prebid[j].dealId, pair.expected[j].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is set to ' + pair.expected[i].dealId); - } - } - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_deal_2_2: response for single slot with multi size, some deal resposne returned and the rest non deal response -> all bid fetched, only deal response has dealID', function () { - var sizeCount = 2; - var configuredBids = IndexUtils.createBidSlots(1, sizeCount); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var optionalResponseParam = [ - [ - { ext: { deal: 'deal1', dealid: 'ixDealID1', dealname: 'deal name 1' } } // first slot first size - // No deal on first slot second size - ] - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam); - cygnus_index_parse_res(asResponse); - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - - for (var j = 0; j < pair.expected.length; j++) { - assert.equal(pair.prebid[j].siteID, pair.expected[j].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[i].siteID); - assert.equal(pair.prebid[j].bidderCode, pair.expected[j].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[i].bidderCode); - assert.equal(pair.prebid[j].width, pair.expected[j].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[i].width); - assert.equal(pair.prebid[j].height, pair.expected[j].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[i].height); - assert.equal(pair.prebid[j].ad, pair.expected[j].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[i].ad); - assert.equal(pair.prebid[j].cpm, pair.expected[j].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[i].cpm); - if (i === 0) { - assert.equal(pair.prebid[j].dealId, pair.expected[j].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is set to ' + pair.expected[i].dealId); - } else { - assert.isUndefined(pair.prebid[j].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is not set'); - } - } - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_deal_2_3: response for single slot with multi size, all returned as non-deal response -> all bid fetched, no response has dealID', function () { - var sizeCount = 2; - var configuredBids = IndexUtils.createBidSlots(1, sizeCount); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var optionalResponseParam = [ - [ - {}, - {} - // No deal on first slot first size - // No deal on first slot second size - ] - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam); - - cygnus_index_parse_res(asResponse); - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - for (var j = 0; j < pair.expected.length; j++) { - assert.equal(pair.prebid[i].siteID, pair.expected[i].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[i].siteID); - assert.equal(pair.prebid[i].bidderCode, pair.expected[i].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[i].bidderCode); - assert.equal(pair.prebid[i].width, pair.expected[i].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[i].width); - assert.equal(pair.prebid[i].height, pair.expected[i].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[i].height); - assert.equal(pair.prebid[i].ad, pair.expected[i].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[i].ad); - assert.equal(pair.prebid[i].cpm, pair.expected[i].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[i].cpm); - assert.isUndefined(pair.prebid[i].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is not set'); - } - } - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_deal_3_1: multi slots, all responses contain deal -> all bid fetched into prebid as deal bid', function () { - var slotCount = 2; - var configuredBids = IndexUtils.createBidSlots(slotCount, 1); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var optionalResponseParam = [ - [ - { ext: { dealid: 'ixDeal1' } } // first slot first size - ], - [ - { ext: { dealid: 'ixDeal2' } } // second slot first size - ] - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam); - cygnus_index_parse_res(asResponse); - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - assert.equal(pair.prebid[0].siteID, pair.expected[0].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[0].siteID); - assert.equal(pair.prebid[0].bidderCode, pair.expected[0].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[0].bidderCode); - assert.equal(pair.prebid[0].width, pair.expected[0].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[0].width); - assert.equal(pair.prebid[0].height, pair.expected[0].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[0].height); - assert.equal(pair.prebid[0].ad, pair.expected[0].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[0].ad); - assert.equal(pair.prebid[0].cpm, pair.expected[0].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[0].cpm); - assert.equal(pair.prebid[0].dealId, pair.expected[0].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is set to ' + pair.expected[0].dealId); - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_deal_3_2: multi slots, some responses contain deal -> all bid fetched, only deal response has dealID', function () { - var slotCount = 2; - var configuredBids = IndexUtils.createBidSlots(slotCount, 1); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var optionalResponseParam = [ - [ - { ext: { dealid: 'ixDeal1' } } // first slot first size - ], - [ - {} - // no deal on second slot first size - ] - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam); - cygnus_index_parse_res(asResponse); - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - var count = 0; - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - assert.equal(pair.prebid[0].siteID, pair.expected[0].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[0].siteID); - assert.equal(pair.prebid[0].bidderCode, pair.expected[0].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[0].bidderCode); - assert.equal(pair.prebid[0].width, pair.expected[0].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[0].width); - assert.equal(pair.prebid[0].height, pair.expected[0].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[0].height); - assert.equal(pair.prebid[0].ad, pair.expected[0].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[0].ad); - assert.equal(pair.prebid[0].cpm, pair.expected[0].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[0].cpm); - if (count === 0) { // if first slot, check deal parameter - assert.equal(pair.prebid[0].dealId, pair.expected[0].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is set to ' + pair.expected[0].dealId); - } else { - assert.isUndefined(pair.prebid[0].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is not defined'); - } - count++; - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_response_deal_3_3: multi slots, no responses contain deal -> all bid fetched, no response has dealID ', function () { - var slotCount = 2; - var configuredBids = IndexUtils.createBidSlots(slotCount, 1); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var optionalResponseParam = [ - [ - {} - // no deal on first slot first size - ], - [ - {} - // no deal on second slot first size - ] - ]; - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, undefined, optionalResponseParam); - cygnus_index_parse_res(asResponse); - var expectedAdapterResponse = IndexUtils.getExpectedAdaptorResponse(configuredBids, asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - - var prebidResponsePair = IndexUtils.matchOnPlacementCode(expectedAdapterResponse, adapterResponse); - - for (var i = 0; i < prebidResponsePair.matched.length; i++) { - var pair = prebidResponsePair.matched[i]; - assert.equal(pair.prebid[0].siteID, pair.expected[0].siteID, 'adapter response for ' + pair.placementCode + ' siteID is set to ' + pair.expected[0].siteID); - assert.equal(pair.prebid[0].bidderCode, pair.expected[0].bidderCode, 'adapter response for ' + pair.placementCode + ' bidderCode is set to ' + pair.expected[0].bidderCode); - assert.equal(pair.prebid[0].width, pair.expected[0].width, 'adapter response for ' + pair.placementCode + ' width is set to ' + pair.expected[0].width); - assert.equal(pair.prebid[0].height, pair.expected[0].height, 'adapter response for ' + pair.placementCode + ' height is set to ' + pair.expected[0].height); - assert.equal(pair.prebid[0].ad, pair.expected[0].ad, 'adapter response for ' + pair.placementCode + ' ad is set to ' + pair.expected[0].ad); - assert.equal(pair.prebid[0].cpm, pair.expected[0].cpm, 'adapter response for ' + pair.placementCode + ' cpm is set to ' + pair.expected[0].cpm); - assert.isUndefined(pair.prebid[0].dealId, 'adapter response for ' + pair.placementCode + ' deaiid is not defined'); - } - - assert.equal(prebidResponsePair.unmatched.expected.length, 0, 'All AS bid response translated to Adapter response for prebid'); - assert.equal(prebidResponsePair.unmatched.prebid.length, 0, 'All Adapter response for prebid is from AS bid'); - }); - - it('test_prebid_indexAdapter_tier: one slot with multiple tier -> all tier bids are fetched into prebid', function() { - var slotConfig = { - tier2SiteID: IndexUtils.DefaultSiteID + 1, - tier3SiteID: IndexUtils.DefaultSiteID + 2, - }; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], slotConfig), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - - assert.equal(sidMatched.matched.length, 3, 'Three slots are configured and sent to AS'); - // check normal site id - var normalSitePair = sidMatched.matched[0]; - - var expectedSlotID = normalSitePair.configured.params.id + '_1'; - assert.equal(normalSitePair.sent.ext.sid, expectedSlotID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedSlotID); - assert.isString(normalSitePair.sent.ext.sid, 'type of slot ID is string'); - - var expectedSiteID = normalSitePair.configured.params.siteID; - assert.equal(normalSitePair.sent.ext.siteID, expectedSiteID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedSiteID); - assert.isNumber(normalSitePair.sent.ext.siteID, 'site ID is integer'); - - // check tier 1 site id - var tier2SitePair = sidMatched.matched[1]; - var expectedTierSlotID = 'T1_' + tier2SitePair.configured.params.id + '_1'; - assert.equal(tier2SitePair.sent.ext.sid, expectedTierSlotID, 'request ' + tier2SitePair.name + ' site ID is set to ' + expectedTierSlotID); - assert.isString(tier2SitePair.sent.ext.sid, 'type of slot ID is string'); - - var expectedTierSiteID = tier2SitePair.configured.params.tier2SiteID; - assert.equal(tier2SitePair.sent.ext.siteID, expectedTierSiteID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedTierSiteID); - assert.isNumber(tier2SitePair.sent.ext.siteID, 'site ID is integer'); - - // check tier 2 site id - var tier3SitePair = sidMatched.matched[2]; - var expectedTierSlotID = 'T2_' + tier3SitePair.configured.params.id + '_1'; - assert.equal(tier3SitePair.sent.ext.sid, expectedTierSlotID, 'request ' + tier3SitePair.name + ' site ID is set to ' + expectedTierSlotID); - assert.isString(tier3SitePair.sent.ext.sid, 'type of slot ID is string'); - - var expectedTier3SiteID = tier3SitePair.configured.params.tier3SiteID; - assert.equal(tier3SitePair.sent.ext.siteID, expectedTier3SiteID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedTier3SiteID); - assert.isNumber(tier3SitePair.sent.ext.siteID, 'site ID is integer'); - - // check unsent bids - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - }); - - it('test_prebid_indexAdapter_callback_bids: callback function defined with bids -> calls callback function with bids', function () { - var callbackCalled = false; - var callback_requestID; - var callback_slots; - window.cygnus_index_args['callback'] = function(requestID, slots) { - callbackCalled = true; - callback_requestID = requestID; - callback_slots = slots; - } - - var configuredBids = IndexUtils.createBidSlots(1, 1); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON); - cygnus_index_parse_res(asResponse); - - assert.equal(callbackCalled, true, 'callback function is called'); - assert.equal(callback_requestID, requestJSON.r.id, 'callback requestID matches with actual request ID: ' + requestJSON.r.id); - assert.equal(callback_slots.length, 1, 'callback slots include one slot'); - }); - - it('test_prebid_indexAdapter_callback_nobids: callback function defined with no bids -> calls callback function without bids', function () { - var callbackCalled = false; - var callback_requestID; - var callback_slots; - window.cygnus_index_args['callback'] = function(requestID, slots) { - callbackCalled = true; - callback_requestID = requestID; - callback_slots = slots; - } - - var configuredBids = IndexUtils.createBidSlots(1, 1); - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, [[true]]); // pass on bid - cygnus_index_parse_res(asResponse); - - assert.equal(callbackCalled, true, 'callback function is called'); - assert.equal(callback_requestID, requestJSON.r.id, 'callback requestID matches with actual request ID: ' + requestJSON.r.id); - assert.isUndefined(callback_slots, 'callback slot is undefined because all bids passed on bid'); - }); - - it('test_prebid_indexAdapter_response_sizeID_1: multiple prebid size slot, index slots with size for all prebid slots -> all size in AS request, no size ID', function () { - var slotID_1 = '52'; - var slotID_2 = '53'; - var slotSizes_1 = IndexUtils.supportedSizes[0]; - var slotSizes_2 = IndexUtils.supportedSizes[1]; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + slotID_1, slotID_1, [ slotSizes_1, slotSizes_2 ], { slotSize: slotSizes_1 }), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + slotID_2, slotID_2, [ slotSizes_1, slotSizes_2 ], { siteID: IndexUtils.DefaultSiteID + 1 }) - ]; - - adapter.callBids({ bids: configuredBids }); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - var asResponse = IndexUtils.getBidResponse(configuredBids, requestJSON, undefined, undefined, [[true]]); // pass on bid - cygnus_index_parse_res(asResponse); - - var adapterResponse = {}; - - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - } - }); -}); diff --git a/test/spec/modules/indexExchangeBidAdapter_validation_spec.js b/test/spec/modules/indexExchangeBidAdapter_validation_spec.js deleted file mode 100644 index 46a1996cc8a..00000000000 --- a/test/spec/modules/indexExchangeBidAdapter_validation_spec.js +++ /dev/null @@ -1,1607 +0,0 @@ -import Adapter from '../../../modules/indexExchangeBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import adLoader from '../../../src/adloader'; - -var assert = require('chai').assert; -var IndexUtils = require('../../helpers/index_adapter_utils.js'); -var HeaderTagRequest = '/cygnus'; -var ADAPTER_CODE = 'indexExchange'; - -window.pbjs = window.pbjs || {}; - -describe('indexExchange adapter - Validation', function () { - let adapter; - let sandbox; - - beforeEach(function() { - window._IndexRequestData = {}; - _IndexRequestData.impIDToSlotID = {}; - _IndexRequestData.reqOptions = {}; - _IndexRequestData.targetIDToResp = {}; - window.cygnus_index_args = {}; - - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - sandbox.stub(adLoader, 'loadScript'); - sandbox.stub(bidManager, 'addBidResponse'); - }); - - afterEach(function() { - sandbox.restore(); - }); - - it('test_prebid_indexAdapter_sizeValidation_1: request slot has supported and unsupported size -> unsupported size ignored in IX demand request', function () { - // create 2 sizes for 1 slot, 1 for supported size, the other is not supported - var unsupportedSize = IndexUtils.unsupportedSizes[0]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0], unsupportedSize ]) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, unsupportedSize, 'configured bid not in impression obj size width is' + JSON.stringify(unsupportedSize)); - - // checking bid manager responses. Only one bid back into bidmanager because one size is unsupported - var adapterResponse = {}; - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - }; - assert.deepEqual(Object.keys(adapterResponse), [IndexUtils.DefaultPlacementCodePrefix], 'bid response from placement code that is configured'); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix].length, 1, 'one response back returned for placement ' + IndexUtils.DefaultPlacementCodePrefix); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].bidderCode, ADAPTER_CODE, 'bidder code match with adapter\'s name'); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].statusMessage, 'Bid returned empty or error response', 'pass on bid message'); - }); - - it('test_prebid_indexAdapter_sizeValidation_2_1: some slot has unsupported size -> unsupported slot ignored in IX demand request', function () { - // create 2 slot, 1 for supported size, the other is not supported - var unsupportedSize = IndexUtils.unsupportedSizes[0]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'supported', 'slot_1', [ IndexUtils.supportedSizes[0], ], { siteID: IndexUtils.DefaultSiteID }), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'unspported', 'slot_2', [ unsupportedSize ], { siteID: IndexUtils.DefaultSiteID + 1}) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, unsupportedSize, 'configured bid not in impression obj size width is' + JSON.stringify(unsupportedSize)); - assert.equal(sidMatched.unmatched.configured[0].params.id, 'slot_2', 'configured bid not in impression obj id is slot_2'); - assert.equal(sidMatched.unmatched.configured[0].params.siteID, IndexUtils.DefaultSiteID + 1, 'configured bid not in impression obj siteID is ' + (IndexUtils.DefaultSiteID + 1)); - }); - - it('test_prebid_indexAdapter_sizeValidation_2_2: multiple slots with sinle size, all slot has supported size -> all slots are sent to IX demand', function () { - // create 2 slot, 1 for supported size, the other is not supported - var unsupportedSize = IndexUtils.unsupportedSizes[0]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'supported1', 'slot_1', [ IndexUtils.supportedSizes[0] ], { siteID: IndexUtils.DefaultSiteID }), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'supported2', 'slot_2', [ IndexUtils.supportedSizes[1] ], { siteID: IndexUtils.DefaultSiteID + 1}) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 0, '0 configured bid is not in impression Obj'); - }); - - it('test_prebid_indexAdapter_sizeValidation_2_3: multiple slots with sinle size, all slot has unsupported size -> all slots are ignored', function () { - // create 2 slot, 1 for supported size, the other is not supported - var unsupportedSize = IndexUtils.unsupportedSizes[0]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'unsupported1', 'slot_1', [ IndexUtils.unsupportedSizes[0] ], { siteID: IndexUtils.DefaultSiteID }), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'unsupported2', 'slot_2', [ IndexUtils.unsupportedSizes[1] ], { siteID: IndexUtils.DefaultSiteID + 1}) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isFalse(adLoader.loadScript.called, 'no request made to IX demand'); - }); - - it('test_prebid_indexAdapter_sizeValidation_3_1: one slot has supported, unsupported, supported size -> unsupported slot ignored in IX demand request', function () { - // create 2 slot, 1 for supported size, the other is not supported - var unsupportedSize = IndexUtils.unsupportedSizes[0]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'somesupported', 'slot_1', [ IndexUtils.supportedSizes[0], unsupportedSize, IndexUtils.supportedSizes[1] ], { siteID: IndexUtils.DefaultSiteID }), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'allsupported', 'slot_2', [ IndexUtils.supportedSizes[2], IndexUtils.supportedSizes[3] ], { siteID: IndexUtils.DefaultSiteID + 1}) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, unsupportedSize, 'configured bid not in impression obj size width is' + JSON.stringify(unsupportedSize)); - assert.equal(sidMatched.unmatched.configured[0].params.id, 'slot_1', 'configured bid not in impression obj id is slot_1'); - assert.equal(sidMatched.unmatched.configured[0].params.siteID, IndexUtils.DefaultSiteID, 'configured bid not in impression obj siteID is ' + (IndexUtils.DefaultSiteID)); - }); - - it('test_prebid_indexAdapter_sizeValidation_3_2: one slot has unsupported, supported, unsupported size -> unsupported slot ignored in IX demand request', function () { - // create 2 slot, 1 for supported size, the other is not supported - var unsupportedSize1 = IndexUtils.unsupportedSizes[0]; - var unsupportedSize2 = IndexUtils.unsupportedSizes[1]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'somesupported', 'slot_1', [ unsupportedSize1, IndexUtils.supportedSizes[1], unsupportedSize2 ], { siteID: IndexUtils.DefaultSiteID }), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'allsupported', 'slot_2', [ IndexUtils.supportedSizes[2], IndexUtils.supportedSizes[3] ], { siteID: IndexUtils.DefaultSiteID + 1}) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.equal(sidMatched.unmatched.configured.length, 2, '2 configured bid is not in impression Obj'); - - assert.equal(sidMatched.unmatched.configured[0].size, unsupportedSize1, 'configured bid not in impression obj size width is' + JSON.stringify(unsupportedSize1)); - assert.equal(sidMatched.unmatched.configured[0].params.id, 'slot_1', 'configured bid not in impression obj id is slot_1'); - assert.equal(sidMatched.unmatched.configured[0].params.siteID, IndexUtils.DefaultSiteID, 'configured bid not in impression obj siteID is ' + (IndexUtils.DefaultSiteID)); - - assert.equal(sidMatched.unmatched.configured[1].size, unsupportedSize2, 'configured bid not in impression obj size width is' + JSON.stringify(unsupportedSize2)); - assert.equal(sidMatched.unmatched.configured[1].params.id, 'slot_1', 'configured bid not in impression obj id is slot_1'); - assert.equal(sidMatched.unmatched.configured[1].params.siteID, IndexUtils.DefaultSiteID, 'configured bid not in impression obj siteID is ' + (IndexUtils.DefaultSiteID)); - }); - - it('test_prebid_indexAdapter_sizeValidation_3_3: multiple slots, all slots have supported size -> all slots are included in IX demand request', function () { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'allsupported1', 'slot_1', [ IndexUtils.supportedSizes[0], IndexUtils.supportedSizes[1] ], { siteID: IndexUtils.DefaultSiteID }), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + 'allsupported2', 'slot_2', [ IndexUtils.supportedSizes[2], IndexUtils.supportedSizes[3] ], { siteID: IndexUtils.DefaultSiteID + 1}) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.equal(sidMatched.unmatched.configured.length, 0, '0 configured bid is not in impression Obj'); - }); - - it('test_prebid_indexAdapter_sizeValidation_3_4: multiple slots, all slots have unsupported size -> no slots are sent to IX demand', function () { - var firstPlacement = IndexUtils.DefaultPlacementCodePrefix + 'allsupported1'; - var secondPlacement = IndexUtils.DefaultPlacementCodePrefix + 'allsupported2'; - var configuredBids = [ - IndexUtils.createBidSlot(firstPlacement, 'slot_1', [ IndexUtils.unsupportedSizes[0], IndexUtils.unsupportedSizes[1] ], { siteID: IndexUtils.DefaultSiteID }), - IndexUtils.createBidSlot(secondPlacement, 'slot_2', [ IndexUtils.unsupportedSizes[2], IndexUtils.unsupportedSizes[3] ], { siteID: IndexUtils.DefaultSiteID + 1}) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isFalse(adLoader.loadScript.called, 'No request to IX demand'); - - // checking bid manager responses. Only one bid back into bidmanager because one size is unsupported - var adapterResponse = {}; - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - }; - assert.deepEqual(Object.keys(adapterResponse), [firstPlacement, secondPlacement], 'bid response from placement code that is configured'); - assert.equal(adapterResponse[firstPlacement].length, 2, 'two response back returned for placement ' + firstPlacement); - assert.equal(adapterResponse[firstPlacement][0].bidderCode, ADAPTER_CODE, 'bidder code match with adapter\'s name'); - assert.equal(adapterResponse[firstPlacement][0].statusMessage, 'Bid returned empty or error response', 'pass on bid message'); - assert.equal(adapterResponse[firstPlacement][1].bidderCode, ADAPTER_CODE, 'bidder code match with adapter\'s name'); - assert.equal(adapterResponse[firstPlacement][1].statusMessage, 'Bid returned empty or error response', 'pass on bid message'); - assert.equal(adapterResponse[secondPlacement].length, 2, 'two response back returned for placement ' + secondPlacement); - assert.equal(adapterResponse[secondPlacement][0].bidderCode, ADAPTER_CODE, 'bidder code match with adapter\'s name'); - assert.equal(adapterResponse[secondPlacement][0].statusMessage, 'Bid returned empty or error response', 'pass on bid message'); - assert.equal(adapterResponse[secondPlacement][1].bidderCode, ADAPTER_CODE, 'bidder code match with adapter\'s name'); - assert.equal(adapterResponse[secondPlacement][1].statusMessage, 'Bid returned empty or error response', 'pass on bid message'); - }); - - it('test_prebid_indexAdapter_param_timeout_integer: timeout is integer -> t parameter that matches with the integer', function () { - var testTimeout = 100; // integer timeout - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], { timeout: testTimeout }), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.equal(requestJSON.t, testTimeout, 't parameter matches timeout and is included in AS request parameter'); - }); - - it('test_prebid_indexAdapter_param_timeout_quoted_integer: timeout is quoted integer -> t parameter that matches with the integer', function () { - var testTimeout = '100'; // quoted integer timeout - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], { timeout: testTimeout }), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.equal(requestJSON.t, testTimeout, 't parameter matches timeout and is included in AS request parameter'); - }); - - it('test_prebid_indexAdapter_param_timeout_float: timeout is float number -> t parameter is not included in AS request', function () { - var testTimeout = 1.234; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], { timeout: testTimeout }), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isUndefined(requestJSON.t, 't parameter is not included in AS request parameter'); - }); - - it('test_prebid_indexAdapter_param_timeout_float: timeout is float number -> t parameter is not included in AS request', function () { - var testTimeout = 1.234; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], { timeout: testTimeout }), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isUndefined(requestJSON.t, 't parameter is not included in AS request parameter'); - }); - - it('test_prebid_indexAdapter_param_timeout_string: timeout is string -> t parameter is not included in AS request', function () { - var testTimeout = 'string'; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], { timeout: testTimeout }), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isUndefined(requestJSON.t, 't parameter is not included in AS request parameter'); - }); - - it('test_prebid_indexAdapter_param_timeout_array: timeout is array -> t parameter is not included in AS request', function () { - var testTimeout = [ 'abc' ]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], { timeout: testTimeout }), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isUndefined(requestJSON.t, 't parameter is not included in AS request parameter'); - }); - - it('test_prebid_indexAdapter_param_timeout_hash: timeout is hash -> t parameter is not included in AS request', function () { - var testTimeout = { 'timeout': 100 }; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], { timeout: testTimeout }), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isUndefined(requestJSON.t, 't parameter is not included in AS request parameter'); - }); - - it('test_prebid_indexAdapter_param_timeout_zero: timeout is zero -> t parameter is not included in AS request', function () { - var testTimeout = 0; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], { timeout: testTimeout }), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isUndefined(requestJSON.t, 't parameter is not included in AS request parameter'); - }); - - it('test_prebid_indexAdapter_param_timeout_negative: timeout is negative integer -> t parameter is not included in AS request', function () { - var testTimeout = -100; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], { timeout: testTimeout }), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isUndefined(requestJSON.t, 't parameter is not included in AS request parameter'); - }); - - it('test_prebid_indexAdapter_param_timeout_too_big: timeout is bigger than AS max timeout -> t parameter is not included in AS request', function () { - var testTimeout = 25000; // very large timeout - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], { timeout: testTimeout }), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.equal(requestJSON.t, testTimeout, 't parameter matches timeout and is included in AS request parameter'); - }); - - it('test_prebid_indexAdapter_param_timeout_missing: timeout is missing -> t parameter is not included in AS request', function () { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ]), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isUndefined(requestJSON.t, 't parameter is not included in AS request parameter'); - }); - - it('test_prebid_indexAdapter_param_timeout_empty_string: timeout is empty string -> t parameter is not included in AS request', function () { - var testTimeout = ''; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], { timeout: testTimeout}), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isUndefined(requestJSON.t, 't parameter is not included in AS request parameter'); - }); - - var test_indexAdapter_slotid = [ - { - 'testname': 'test_prebid_indexAdapter_slotid_integer: slot ID is integer -> slot ID sent to AS in string', - 'slotID': 123, - 'expected': 'pass' - }, - { - 'testname': 'test_prebid_indexAdapter_slotid_quoted_integer: slot ID is quoted_integer -> slot ID sent to AS in string', - 'slotID': '123', - 'expected': 'pass' - }, - { - 'testname': 'test_prebid_indexAdapter_slotid_float: slot ID is float -> slot ID sent to AS in string', - 'slotID': 123.45, - 'expected': 'pass' - }, - { - 'testname': 'test_prebid_indexAdapter_slotid_string: slot ID is string -> slot ID sent to AS in string', - 'slotID': 'string', - 'expected': 'pass' - }, - { - 'testname': 'test_prebid_indexAdapter_slotid_array: slot ID is array -> slot is not sent to AS', - 'slotID': [ 'arrayelement1', 'arrayelement2' ], - 'expected': 'fail' - }, - { - 'testname': 'test_prebid_indexAdapter_slotid_hash: slot ID is hash -> slot is not sent to AS', - 'slotID': { 'hashName': 'hashKey' }, - 'expected': 'fail' - }, - { - 'testname': 'test_prebid_indexAdapter_slotid_zero: slot ID is zero integer -> slot ID sent to AS in string', - 'slotID': 0, - 'expected': 'pass' - }, - { - 'testname': 'test_prebid_indexAdapter_slotid_negative: slot ID is negative integer -> slot ID sent to AS in string', - 'slotID': -100, - 'expected': 'pass' - }, - { - 'testname': 'test_prebid_indexAdapter_slotid_undefined: slot ID is undefined -> slot is not sent to AS', - 'slotID': undefined, - 'expected': 'fail' - }, - { - 'testname': 'test_prebid_indexAdapter_slotid_missing: slot ID is missing -> slot is not sent to AS', - 'param': { 'missingSlotID': true}, - 'expected': 'invalid' - } - ]; - - function base_prebid_indexAdapter_slotid (testname, slotID, expected, param) { - it(testname, function() { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, slotID, [ IndexUtils.supportedSizes[0] ], param), - ]; - adapter.callBids({ bids: configuredBids }); - var adapterResponse = {}; - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - }; - if (expected == 'pass') { - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - var actualSlotID = pair.sent.ext.sid; - var expectedSlotID = pair.configured.params.id + '_1'; - assert.equal(actualSlotID, expectedSlotID, 'request ' + pair.name + ' slot ID is set to ' + expectedSlotID); - assert.isString(actualSlotID, 'slotID is string'); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.deepEqual(Object.keys(adapterResponse), [], 'no explicit pass on bid'); - } else if (expected == 'invalid') { - // case where callBids throws out request due to missing params - assert.isFalse(adLoader.loadScript.called, 'No request to AS') - assert.deepEqual(Object.keys(adapterResponse), [IndexUtils.DefaultPlacementCodePrefix], 'bid response from placement code that is configured'); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix].length, 1, 'one response back returned for placement ' + IndexUtils.DefaultPlacementCodePrefix); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].bidderCode, ADAPTER_CODE, 'bidder code match with adapter\'s name'); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].statusMessage, 'Bid returned empty or error response', 'pass on bid message'); - } else { - assert.strictEqual(typeof indexBidRequest, 'undefined', 'No request to AS'); - assert.deepEqual(Object.keys(adapterResponse), [IndexUtils.DefaultPlacementCodePrefix], 'bid response from placement code that is configured'); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix].length, 1, 'one response back returned for placement ' + IndexUtils.DefaultPlacementCodePrefix); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].bidderCode, ADAPTER_CODE, 'bidder code match with adapter\'s name'); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].statusMessage, 'Bid returned empty or error response', 'pass on bid message'); - } - }); - }; - - for (var i = 0; i < test_indexAdapter_slotid.length; i++) { - var test = test_indexAdapter_slotid[i]; - base_prebid_indexAdapter_slotid(test.testname, test.slotID, test.expected, test.param); - } - - it('test_prebid_indexAdapter_slotid_multiple_slot: uniqueness for multiple slots -> all slots in ad server request with unique slot id', function() { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ]), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_2', [ IndexUtils.supportedSizes[1] ]), - ]; - adapter.callBids({ bids: configuredBids }); - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - var actualSlotID = pair.sent.ext.sid; - var expectedSlotID = pair.configured.params.id + '_1'; - assert.equal(actualSlotID, expectedSlotID, 'request ' + pair.name + ' slot ID is set to ' + expectedSlotID); - assert.isString(actualSlotID, 'slotID is string'); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - }); - - it('test_prebid_indexAdapter_slotid_multiple_same: same across some slots -> all slots in ad server request with same slot id', function() { - var slotName = 'slot_same'; - var secondSlotSize = IndexUtils.supportedSizes[1]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, slotName, [ IndexUtils.supportedSizes[0] ]), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, slotName, [ secondSlotSize ]), - ]; - adapter.callBids({ bids: configuredBids }); - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - var actualSlotID = pair.sent.ext.sid; - var expectedSlotID = pair.configured.params.id + '_1'; - assert.equal(actualSlotID, expectedSlotID, 'request ' + pair.name + ' slot ID is set to ' + expectedSlotID); - assert.isString(actualSlotID, 'slotID is string'); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.equal(sidMatched.unmatched.configured.length, 1, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, secondSlotSize, 'configured bid not in impression obj size width is' + JSON.stringify(secondSlotSize)); - assert.equal(sidMatched.unmatched.configured[0].params.id, slotName, 'slot name is ' + slotName); - }); - - var test_indexAdapter_siteid = [ - { - 'testname': 'test_prebid_indexAdapter_siteid_integer: site ID is integer -> siteID ID sent to AS as integer', - 'param': { 'siteID': 12345 }, - 'expected': 'pass', - }, - { - 'testname': 'test_prebid_indexAdapter_siteid_quoted_integer: site ID is quoted integer -> siteID ID sent to AS as integer', - 'param': { 'siteID': '12345' }, - 'expected': 'pass', - }, - { - 'testname': 'test_prebid_indexAdapter_siteid_float: site ID is float -> slot is ignored', - 'param': { 'siteID': 12.345 }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_siteid_string: site ID is string -> slot is ignored', - 'param': { 'siteID': 'string' }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_siteid_array: site ID is array with int -> siteID sent to AS as integer', - 'param': { 'siteID': [ 12345 ] }, - 'expected': 'pass', - }, - { - 'testname': 'test_prebid_indexAdapter_siteid_array: site ID is array with quoted int -> siteID sent to AS as integer', - 'param': { 'siteID': [ '12345' ] }, - 'expected': 'pass', - }, - { - 'testname': 'test_prebid_indexAdapter_siteid_array: site ID is array with alpha string -> slot is ignored', - 'param': { 'siteID': [ 'ABC' ] }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_siteid_hash: site ID is hash -> slot is ignored', - 'param': { 'siteID': { 12345: 678 } }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_siteid_zero: site ID is zero integer -> slot is ignored', - 'param': { 'siteID': 0 }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_siteid_negative: site ID is a negative integer -> slot is ignored', - 'param': { 'siteID': -1234 }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_siteid_missing: site ID is missing -> slot is ignored', - 'param': { 'missingSiteID': true }, - 'expected': 'invalid', - }, - ]; - - function base_prebid_indexAdapter_siteid (testname, param, expected) { - it(testname, function() { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], param), - ]; - - adapter.callBids({ bids: configuredBids }); - var adapterResponse = {}; - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - }; - if (expected == 'pass') { - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - var actualSiteID = pair.sent.ext.siteID; - var expectedSiteID = pair.configured.params.siteID; - assert.equal(actualSiteID, expectedSiteID, 'request ' + pair.name + ' site ID is set to ' + expectedSiteID); - assert.isNumber(actualSiteID, 'site ID is integer'); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.deepEqual(Object.keys(adapterResponse), [], 'no explicit pass on bid'); - } else if (expected == 'invalid') { - // case where callBids throws out request due to missing params - assert.isFalse(adLoader.loadScript.called, 'No request to AS'); - assert.deepEqual(Object.keys(adapterResponse), [IndexUtils.DefaultPlacementCodePrefix], 'bid response from placement code that is configured'); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix].length, 1, 'one response back returned for placement ' + IndexUtils.DefaultPlacementCodePrefix); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].bidderCode, ADAPTER_CODE, 'bidder code match with adapter\'s name'); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].statusMessage, 'Bid returned empty or error response', 'pass on bid message'); - } else { - assert.isFalse(adLoader.loadScript.called, 'No request to AS'); - assert.deepEqual(Object.keys(adapterResponse), [IndexUtils.DefaultPlacementCodePrefix], 'bid response from placement code that is configured'); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix].length, 1, 'one response back returned for placement ' + IndexUtils.DefaultPlacementCodePrefix); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].bidderCode, ADAPTER_CODE, 'bidder code match with adapter\'s name'); - assert.equal(adapterResponse[IndexUtils.DefaultPlacementCodePrefix][0].statusMessage, 'Bid returned empty or error response', 'pass on bid message'); - } - }); - }; - - for (var i = 0; i < test_indexAdapter_siteid.length; i++) { - var test = test_indexAdapter_siteid[i]; - base_prebid_indexAdapter_siteid(test.testname, test.param, test.expected); - } - - // TS: case created by PBA-12 - it('test_prebid_indexAdapter_second_siteid_float: site ID is float -> slot is ignored', function() { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + '1', 'slot_1', [ IndexUtils.supportedSizes[0] ]), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix + '2', 'slot_2', [ IndexUtils.supportedSizes[1] ], { 'siteID': 123.45 }), - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - assert.equal(sidMatched.matched.length, 1, 'one slot is configured and sent to AS'); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - var actualSiteID = pair.sent.ext.siteID; - var expectedSiteID = pair.configured.params.siteID; - assert.equal(actualSiteID, expectedSiteID, 'request ' + pair.name + ' site ID is set to ' + expectedSiteID); - assert.isNumber(actualSiteID, 'site ID is integer'); - } - - assert.equal(sidMatched.unmatched.configured.length, 1, 'float site ID configured bid is missing in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - }); - - var test_indexAdapter_tier2siteid = [ - { - 'testname': 'test_prebid_indexAdapter_tier2siteid_integer: tier2 site ID is integer -> siteID ID sent to AS in integer', - 'param': { 'tier2SiteID': 12345 }, - 'expected': 'pass', - }, - { - 'testname': 'test_prebid_indexAdapter_tier2siteid_quoted_integer: tier2 site ID is quoted integer -> siteID ID sent to AS in integer', - 'param': { 'tier2SiteID': '12345' }, - 'expected': 'pass', - }, - { - 'testname': 'test_prebid_indexAdapter_tier2siteid_float: tier2 site ID is float -> slot is ignored', - 'param': { 'tier2SiteID': 12.345 }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_tier2siteid_string: tier2 site ID is string -> slot is ignored', - 'param': { 'tier2SiteID': 'string' }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_tier2siteid_array: tier2 site ID is array -> slot is ignored', - 'param': { 'tier2SiteID': [ 12345 ] }, - 'expected': 'pass', - }, - { - 'testname': 'test_prebid_indexAdapter_tier2siteid_hash: tier2 site ID is hash -> slot is ignored', - 'param': { 'tier2SiteID': { 12345: 678 } }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_tier2siteid_zero: tier2 site ID is zero integer -> slot is ignored', - 'param': { 'tier2SiteID': 0 }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_tier2siteid_negative: tier2 site ID is a negative integer -> slot is ignored', - 'param': { 'tier2SiteID': -1234 }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_tier2siteid_missing: tier2 site ID is missing -> slot is ignored', - 'param': { 'missingtier2SiteID': true }, - 'expected': 'fail', - }, - ]; - function base_prebid_indexAdapter_tier2siteid (testname, param, expected) { - it(testname, function() { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], param), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - - var adapterResponse = {}; - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - }; - if (expected == 'pass') { - assert.equal(sidMatched.matched.length, 2, 'Two slots are configured and sent to AS'); - - // check normal site id - var normalSitePair = sidMatched.matched[0]; - - var expectedSlotID = normalSitePair.configured.params.id + '_1'; - assert.equal(normalSitePair.sent.ext.sid, expectedSlotID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedSlotID); - assert.isString(normalSitePair.sent.ext.sid, 'type of slot ID is string'); - - var expectedSiteID = normalSitePair.configured.params.siteID; - assert.equal(normalSitePair.sent.ext.siteID, expectedSiteID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedSiteID); - assert.isNumber(normalSitePair.sent.ext.siteID, 'site ID is integer'); - - // check tier site id - var tier2SitePair = sidMatched.matched[1]; - var expectedTierSlotID = 'T1_' + tier2SitePair.configured.params.id + '_1'; - assert.equal(tier2SitePair.sent.ext.sid, expectedTierSlotID, 'request ' + tier2SitePair.name + ' site ID is set to ' + expectedTierSlotID); - assert.isString(tier2SitePair.sent.ext.sid, 'type of slot ID is string'); - - var expectedTierSiteID = tier2SitePair.configured.params.tier2SiteID; - assert.equal(tier2SitePair.sent.ext.siteID, expectedTierSiteID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedTierSiteID); - assert.isNumber(tier2SitePair.sent.ext.siteID, 'site ID is integer'); - - // check unsent bids - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.deepEqual(Object.keys(adapterResponse), [], 'no explicit pass on bid'); - } else { - assert.equal(sidMatched.matched.length, 1, 'one slot is configured and sent to AS'); - - // check normal site id - var normalSitePair = sidMatched.matched[0]; - - var expectedSlotID = normalSitePair.configured.params.id + '_1'; - assert.equal(normalSitePair.sent.ext.sid, expectedSlotID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedSlotID); - assert.isString(normalSitePair.sent.ext.sid, 'type of slot ID is string'); - - var expectedSiteID = normalSitePair.configured.params.siteID; - assert.equal(normalSitePair.sent.ext.siteID, expectedSiteID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedSiteID); - assert.isNumber(normalSitePair.sent.ext.siteID, 'site ID is integer'); - - // check unsent bids - if (param.missingtier2SiteID) { - assert.equal(sidMatched.unmatched.configured.length, 0, 'one configured bid is missing in impression Obj'); - } else { - assert.equal(sidMatched.unmatched.configured.length, 1, 'one configured bid is missing in impression Obj'); - } - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.deepEqual(Object.keys(adapterResponse), [], 'no explicit pass on bid'); - } - }); - }; - - for (var i = 0; i < test_indexAdapter_tier2siteid.length; i++) { - var test = test_indexAdapter_tier2siteid[i]; - base_prebid_indexAdapter_tier2siteid(test.testname, test.param, test.expected); - } - - var test_indexAdapter_tier3siteid = [ - { - 'testname': 'test_prebid_indexAdapter_tier3siteid_integer: tier3 site ID is integer -> siteID ID sent to AS in integer', - 'param': { 'tier3SiteID': 12345 }, - 'expected': 'pass', - }, - { - 'testname': 'test_prebid_indexAdapter_tier3siteid_quoted_integer: tier3 site ID is quoted integer -> siteID ID sent to AS in integer', - 'param': { 'tier3SiteID': '12345' }, - 'expected': 'pass', - }, - { - 'testname': 'test_prebid_indexAdapter_tier3siteid_float: tier3 site ID is float -> slot is ignored', - 'param': { 'tier3SiteID': 12.345 }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_tier3siteid_string: tier3 site ID is string -> slot is ignored', - 'param': { 'tier3SiteID': 'string' }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_tier3siteid_array: tier3 site ID is array -> slot is ignored', - 'param': { 'tier3SiteID': [ 12345 ] }, - 'expected': 'pass', - }, - { - 'testname': 'test_prebid_indexAdapter_tier3siteid_hash: tier3 site ID is hash -> slot is ignored', - 'param': { 'tier3SiteID': { 12345: 678 } }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_tier3siteid_zero: tier3 site ID is zero integer -> slot is ignored', - 'param': { 'tier3SiteID': 0 }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_tier3siteid_negative: tier3 site ID is a negative integer -> slot is ignored', - 'param': { 'tier3SiteID': -1234 }, - 'expected': 'fail', - }, - { - 'testname': 'test_prebid_indexAdapter_tier3siteid_missing: tier3 site ID is missing -> slot is ignored', - 'param': { 'missingtier3SiteID': true }, - 'expected': 'fail', - }, - ]; - function base_prebid_indexAdapter_tier3siteid (testname, param, expected) { - it(testname, function() { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0] ], param), - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - - var adapterResponse = {}; - for (var i = 0; i < bidManager.addBidResponse.callCount; i++) { - var adUnitCode = bidManager.addBidResponse.getCall(i).args[0]; - var bid = bidManager.addBidResponse.getCall(i).args[1]; - - if (typeof adapterResponse[adUnitCode] === 'undefined') { - adapterResponse[adUnitCode] = []; - }; - adapterResponse[adUnitCode].push(bid); - }; - if (expected == 'pass') { - assert.equal(sidMatched.matched.length, 2, 'Two slots are configured and sent to AS'); - - // check normal site id - var normalSitePair = sidMatched.matched[0]; - - var expectedSlotID = normalSitePair.configured.params.id + '_1'; - assert.equal(normalSitePair.sent.ext.sid, expectedSlotID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedSlotID); - assert.isString(normalSitePair.sent.ext.sid, 'type of slot ID is string'); - - var expectedSiteID = normalSitePair.configured.params.siteID; - assert.equal(normalSitePair.sent.ext.siteID, expectedSiteID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedSiteID); - assert.isNumber(normalSitePair.sent.ext.siteID, 'site ID is integer'); - - // check tier site id - var tier3SitePair = sidMatched.matched[1]; - var expectedTierSlotID = 'T2_' + tier3SitePair.configured.params.id + '_1'; - assert.equal(tier3SitePair.sent.ext.sid, expectedTierSlotID, 'request ' + tier3SitePair.name + ' site ID is set to ' + expectedTierSlotID); - assert.isString(tier3SitePair.sent.ext.sid, 'type of slot ID is string'); - - var expectedTierSiteID = tier3SitePair.configured.params.tier3SiteID; - assert.equal(tier3SitePair.sent.ext.siteID, expectedTierSiteID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedTierSiteID); - assert.isNumber(tier3SitePair.sent.ext.siteID, 'site ID is integer'); - - // check unsent bids - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.deepEqual(Object.keys(adapterResponse), [], 'no explicit pass on bid'); - } else { - assert.equal(sidMatched.matched.length, 1, 'one slot is configured and sent to AS'); - - // check normal site id - var normalSitePair = sidMatched.matched[0]; - - var expectedSlotID = normalSitePair.configured.params.id + '_1'; - assert.equal(normalSitePair.sent.ext.sid, expectedSlotID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedSlotID); - assert.isString(normalSitePair.sent.ext.sid, 'type of slot ID is string'); - - var expectedSiteID = normalSitePair.configured.params.siteID; - assert.equal(normalSitePair.sent.ext.siteID, expectedSiteID, 'request ' + normalSitePair.name + ' site ID is set to ' + expectedSiteID); - assert.isNumber(normalSitePair.sent.ext.siteID, 'site ID is integer'); - - // check unsent bids - if (param.missingtier3SiteID) { - assert.equal(sidMatched.unmatched.configured.length, 0, 'one configured bid is missing in impression Obj'); - } else { - assert.equal(sidMatched.unmatched.configured.length, 1, 'one configured bid is missing in impression Obj'); - } - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - - assert.deepEqual(Object.keys(adapterResponse), [], 'no explicit pass on bid'); - } - }); - }; - - for (var i = 0; i < test_indexAdapter_tier3siteid.length; i++) { - var test = test_indexAdapter_tier3siteid[i]; - base_prebid_indexAdapter_tier3siteid(test.testname, test.param, test.expected); - } - - it('test_prebid_indexAdapter_siteID_multiple: multiple slots have same siteIDs -> all slots in ad server request with the same site IDs', function() { - var first_slot = { - slotName: 'slot1', - siteID: 111111, - }; - var second_slot = { - slotName: 'slot2', - siteID: 111111, // same as first slot - }; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, first_slot['slotName'], [ IndexUtils.supportedSizes[0] ], { siteID: first_slot['siteID'] }), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, second_slot['slotName'], [ IndexUtils.supportedSizes[1] ], { siteID: second_slot['siteID'] }), - ]; - - adapter.callBids({ bids: configuredBids }); - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - var expectedSiteID = pair.configured.params.siteID; - var actualSiteID = pair.sent.ext.siteID; - assert.equal(actualSiteID, expectedSiteID, 'request ' + pair.name + ' site ID is set to ' + expectedSiteID); - assert.isNumber(actualSiteID, 'site ID is number'); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - }); - - it('test_prebid_indexAdapter_siteID_different: multiple slots have different siteIDs -> all slots in ad server request with the different site IDs', function() { - var first_slot = { - slotName: 'slot1', - siteID: 111111, - }; - var second_slot = { - slotName: 'slot2', - siteID: 222222, - }; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, first_slot['slotName'], [ IndexUtils.supportedSizes[0] ], { siteID: first_slot['siteID'] }), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, second_slot['slotName'], [ IndexUtils.supportedSizes[1] ], { siteID: second_slot['siteID'] }), - ]; - - adapter.callBids({ bids: configuredBids }); - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - var expectedSiteID = pair.configured.params.siteID; - var actualSiteID = pair.sent.ext.siteID; - assert.equal(actualSiteID, expectedSiteID, 'request ' + pair.name + ' site ID is set to ' + expectedSiteID); - assert.isNumber(actualSiteID, 'site ID is number'); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - }); - - it('test_prebid_indexAdapter_size_singleArr: single sized array -> width and height in integer in request', function () { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', IndexUtils.supportedSizes[0]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSID(expandedBids, impressionObj); - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.configured.length, 0, 'All configured bids are in impression Obj'); - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - }); - - it('test_prebid_indexAdapter_size_singleDim: missing width/height -> size is ignored, no ad server request for bad size', function () { - var oneDimSize = [728]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0], oneDimSize, IndexUtils.supportedSizes[1] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, oneDimSize, 'configured bid not in impression obj size width is' + JSON.stringify(oneDimSize)); - }); - - it('test_prebid_indexAdapter_size_missing: missing size -> slot is ignored, no ad server request', function () { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', []) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isFalse(adLoader.loadScript.called, 'no request made to AS'); - }); - - it('test_prebid_indexAdapter_size_negativeWidth: negative width -> size is ignored, no ad server request for bad size', function () { - var invalidSize = [-728, 90]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0], invalidSize, IndexUtils.supportedSizes[1] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, invalidSize, 'configured bid not in impression obj size width is' + JSON.stringify(invalidSize)); - }); - - it('test_prebid_indexAdapter_size_negativeHeight: negative height -> size is ignored, no ad server request for bad size', function () { - var invalidSize = [728, -90]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0], invalidSize, IndexUtils.supportedSizes[1] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)) - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, invalidSize, 'configured bid not in impression obj size width is' + JSON.stringify(invalidSize)); - }); - - it('test_prebid_indexAdapter_size_quoted: height and width quoted -> invalid size, no ad server request for invalid size', function () { - var otherSize = ['300', '250']; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0], otherSize, IndexUtils.supportedSizes[1] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)); - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 0, '0 configured bid is not in impression Obj'); - }); - - it('test_prebid_indexAdapter_size_float: height and width float -> invalid size, no ad server request for invalid size ', function () { - var otherSize = [300.1, 250]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0], otherSize, IndexUtils.supportedSizes[1] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)); - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, otherSize, 'configured bid not in impression obj size width is' + JSON.stringify(otherSize)); - }); - - it('test_prebid_indexAdapter_size_string_1_pba23: height and width string -> invalid size, no ad server request for invalid size ', function () { - var otherSize = [String(IndexUtils.supportedSizes[0][0]), String(IndexUtils.supportedSizes[0][1])]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[1], otherSize, IndexUtils.supportedSizes[2] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)); - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 0, 'all configured bids are in impression Obj'); - }); - - it('test_prebid_indexAdapter_size_string_2: whole size is string -> invalid size, no ad server request for invalid size ', function () { - var otherSize = 'gallery'; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0], otherSize, IndexUtils.supportedSizes[1] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)); - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, otherSize, 'configured bid not in impression obj size width is' + JSON.stringify(otherSize)); - }); - - it('test_prebid_indexAdapter_size_string_3: entire size structure is string -> no ad server request since size is invalid', function () { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', 'gallery') - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isFalse(adLoader.loadScript.called, 'no request made to AS'); - }); - - it('test_prebid_indexAdapter_size_hash_1: height or width hash -> invalid size, no ad server request for invalid size ', function () { - var otherSize = [{728: 1}, 90]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0], otherSize, IndexUtils.supportedSizes[1] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)); - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, otherSize, 'configured bid not in impression obj size width is' + JSON.stringify(otherSize)); - }); - - it('test_prebid_indexAdapter_size_hash_2: whole size hash -> invalid size, no ad server request for invalid size ', function () { - var otherSize = {728: 1, 90: 1}; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0], otherSize, IndexUtils.supportedSizes[1] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)); - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, otherSize, 'configured bid not in impression obj size width is' + JSON.stringify(otherSize)); - }); - - it('test_prebid_indexAdapter_size_hash_3: entire size structure is hash -> no ad server request since size is invalid', function () { - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', {728: 90}) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isFalse(adLoader.loadScript.called, 'no request made to AS'); - }); - - it('test_prebid_indexAdapter_size_swap: swap size and width for valid so now its invalid -> unsupportedsize, no ad server request for unsupported size ', function () { - var otherSize = [90, 728]; - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ IndexUtils.supportedSizes[0], otherSize, IndexUtils.supportedSizes[1] ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)); - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, otherSize, 'configured bid not in impression obj size width is' + JSON.stringify(otherSize)); - }); - - it('test_prebid_indexAdapter_size_sameWidth: same width for all sizes in a slot -> ad server request only for supported sizes', function () { - var valid1Size = [300, 250]; - var otherSize = [300, 999]; - var valid2Size = [300, 600]; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ valid1Size, otherSize, valid2Size ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)); - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, otherSize, 'configured bid not in impression obj size width is' + JSON.stringify(otherSize)); - }); - - it('test_prebid_indexAdapter_size_sameHeight: same height for all sizes in a slot -> ad server request only for supported sizes', function () { - var valid1Size = [120, 600]; - var otherSize = [999, 600]; - var valid2Size = [300, 600]; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, 'slot_1', [ valid1Size, otherSize, valid2Size ]) - ]; - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - - var expandedBids = configuredBids.map(bid => IndexUtils.expandSizes(bid)); - var sidMatched = IndexUtils.matchBidsOnSize(expandedBids, impressionObj); - - for (var i = 0; i < sidMatched.matched.length; i++) { - var pair = sidMatched.matched[i]; - - assert.equal(pair.sent.banner.w, pair.configured.size[0], 'request ' + pair.name + ' width is set to ' + pair.configured.size[0]); - assert.equal(pair.sent.banner.h, pair.configured.size[1], 'request ' + pair.name + ' width is set to ' + pair.configured.size[1]); - assert.equal(pair.sent.ext.siteID, pair.configured.params.siteID, 'request ' + pair.name + ' siteID is set to ' + pair.configured.params.siteID); - } - - assert.equal(sidMatched.unmatched.sent.length, 0, 'All bids in impression object are from configured bids'); - assert.equal(sidMatched.unmatched.configured.length, 1, '1 configured bid is not in impression Obj'); - assert.equal(sidMatched.unmatched.configured[0].size, otherSize, 'configured bid not in impression obj size width is' + JSON.stringify(otherSize)); - }); - - it('test_prebid_indexAdapter_request_sizeID_validation_1: multiple prebid size slot, index slots with size for all prebid slots, 1 slot is not configured properly -> all size in AS request, except misconfigured slot', function () { - var slotID_1 = 52; - var slotID_2 = 53; - var slotSizes_1 = IndexUtils.supportedSizes[0]; - var slotSizes_2 = IndexUtils.supportedSizes[1]; - - var configuredBids = [ - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, slotID_1, [ slotSizes_1, slotSizes_2 ], { slotSize: [ 728, 'invalid' ] }), - IndexUtils.createBidSlot(IndexUtils.DefaultPlacementCodePrefix, slotID_2, [ slotSizes_1, slotSizes_2 ], { slotSize: slotSizes_2 }) - ]; - - adapter.callBids({ bids: configuredBids }); - - assert.isTrue(adLoader.loadScript.called, 'loadScript get request'); - - assert.include(adLoader.loadScript.firstCall.args[0], HeaderTagRequest, 'request is headertag request'); - - var requestJSON = IndexUtils.parseIndexRequest(adLoader.loadScript.firstCall.args[0]); - assert.isNotNull(requestJSON.r.imp, 'headertag request include impression object'); - - var impressionObj = requestJSON.r.imp; - assert.equal(impressionObj.length, 1, '1 slot is made in the request'); - - assert.equal(impressionObj[0].banner.w, slotSizes_2[0], 'the width made in the request matches with request: ' + slotSizes_2[0]); - assert.equal(impressionObj[0].banner.h, slotSizes_2[1], 'the height made in the request matches with request: ' + slotSizes_2[1]); - assert.equal(impressionObj[0].ext.sid, slotID_2, 'slotID in the request matches with configuration: ' + slotID_2); - assert.equal(impressionObj[0].ext.siteID, IndexUtils.DefaultSiteID, 'siteID in the request matches with request: ' + IndexUtils.DefaultSiteID); - }); -}); diff --git a/test/spec/modules/indexExchangeBidAdapter_video_spec.js b/test/spec/modules/indexExchangeBidAdapter_video_spec.js deleted file mode 100644 index cfbb3aa52ef..00000000000 --- a/test/spec/modules/indexExchangeBidAdapter_video_spec.js +++ /dev/null @@ -1,954 +0,0 @@ -import { expect } from 'chai'; -import Adapter from 'modules/indexExchangeBidAdapter'; -import bidmanager from 'src/bidmanager'; -import adloader from 'src/adloader'; -import * as url from 'src/url'; - -const PREBID_REQUEST = { 'bidderCode': 'indexExchange', 'requestId': '6f4cb846-1901-4fc4-a1a4-5daf58a26e71', 'bidderRequestId': '16940e979c42d4', 'bids': [{ 'bidder': 'indexExchange', 'params': { 'video': { 'siteID': 6, 'playerType': 'HTML5', 'protocols': ['VAST2', 'VAST3'], 'maxduration': 15 } }, 'placementCode': 'video1', 'mediaType': 'video', 'sizes': [640, 480], 'bidId': '2f4e1cc0f992f2', 'bidderRequestId': '16940e979c42d4', 'requestId': '6f4cb846-1901-4fc4-a1a4-5daf58a26e71' }], 'start': 1488236870659, 'auctionStart': 1488236870656, 'timeout': 3000 }; - -const CYGNUS_REQUEST_R_PARAM = { 'id': '16940e979c42d4', 'imp': [{ 'id': '2f4e1cc0f992f2', 'ext': { 'siteID': 6, 'sid': 'pr_1_1_s' }, 'video': { 'protocols': [2, 5, 3, 6], 'maxduration': 15, 'minduration': 0, 'startdelay': 0, 'linearity': 1, 'mimes': ['video/mp4', 'video/webm'], 'w': 640, 'h': 480 } }], 'site': { 'page': 'http://localhost:9876/' }}; - -const PREBID_RESPONSE = { 'bidderCode': 'indexExchange', 'width': 640, 'height': 480, 'statusMessage': 'Bid available', 'adId': '2f4e1cc0f992f2', 'code': 'indexExchange', 'cpm': 10, 'vastUrl': 'http://vast.url', 'descriptionUrl': 'http://vast.url' }; - -const CYGNUS_RESPONSE = { 'seatbid': [{ 'bid': [{ 'crid': '1', 'adomain': ['vastdsp.com'], 'adid': '1', 'impid': '2f4e1cc0f992f2', 'cid': '1', 'id': '1', 'ext': { 'vasturl': 'http://vast.url', 'errorurl': 'http://error.url', 'dspid': 1, 'pricelevel': '_1000', 'advbrandid': 75, 'advbrand': 'Nacho Momma' } }], 'seat': '1' }], 'cur': 'USD', 'id': '16940e979c42d4' }; - -const EMPTY_MESSAGE = 'Bid returned empty or error response'; -const ERROR_MESSAGE = 'Bid returned empty or error response'; -const AVAILABLE_MESSAGE = 'Bid available'; - -const CYGNUS_REQUEST_BASE_URL_INSECURE = 'http://as.casalemedia.com/cygnus?v=8&fn=$$PREBID_GLOBAL$$.handleCygnusResponse&s=6&r='; - -const CYGNUS_REQUEST_BASE_URL_SECURE = 'https://as-sec.casalemedia.com/cygnus?v=8&fn=$$PREBID_GLOBAL$$.handleCygnusResponse&s=6&r='; - -const DEFAULT_MIMES_MAP = { - FLASH: ['video/mp4', 'video/x-flv'], - HTML5: ['video/mp4', 'video/webm'] -}; -const DEFAULT_VPAID_MIMES_MAP = { - FLASH: ['application/x-shockwave-flash'], - HTML5: ['application/javascript'] -}; -const SUPPORTED_API_MAP = { - FLASH: [1, 2], - HTML5: [2] -}; - -describe('indexExchange adapter - Video', () => { - let adapter; - - beforeEach(() => adapter = new Adapter()); - - describe('request to prebid', () => { - let prebidRequest; - - beforeEach(() => { - prebidRequest = JSON.parse(JSON.stringify(PREBID_REQUEST)); - sinon.stub(adloader, 'loadScript'); - }); - - afterEach(() => { - adloader.loadScript.restore(); - }); - - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - - describe('should make request with specified values', () => { - let insecureExpectedUrl = url.parse(CYGNUS_REQUEST_BASE_URL_INSECURE.concat(encodeURIComponent(JSON.stringify(CYGNUS_REQUEST_R_PARAM)))); - - let secureExpectedUrl = url.parse(CYGNUS_REQUEST_BASE_URL_SECURE.concat(encodeURIComponent(JSON.stringify(CYGNUS_REQUEST_R_PARAM)))); - - it('when valid HTML5 required bid request parameters are present', () => { - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.protocol).to.equal(insecureExpectedUrl.protocol); - expect(cygnusRequestUrl.hostname).to.equal(insecureExpectedUrl.hostname); - expect(cygnusRequestUrl.port).to.equal(insecureExpectedUrl.port); - expect(cygnusRequestUrl.pathname).to.equal(insecureExpectedUrl.pathname); - - expect(cygnusRequestUrl.search.v).to.equal(insecureExpectedUrl.search.v); - expect(cygnusRequestUrl.search.s).to.equal(insecureExpectedUrl.search.s); - expect(cygnusRequestUrl.search.fn).to.equal(insecureExpectedUrl.search.fn); - expect(cygnusRequestUrl.search.r).to.exist; - - expect(cygnusRequestUrl.search.r.id).to.equal(prebidRequest.bidderRequestId); - - expect(cygnusRequestUrl.search.r.site.page).to.have.string(CYGNUS_REQUEST_R_PARAM.site.page); - - expect(cygnusRequestUrl.search.r.imp).to.be.a('array'); - expect(cygnusRequestUrl.search.r.imp[0]).to.have.all.keys(Object.keys(CYGNUS_REQUEST_R_PARAM.imp[0])); - - expect(cygnusRequestUrl.search.r.imp[0].id).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].id); - - expect(cygnusRequestUrl.search.r.imp[0].ext.siteID).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].ext.siteID); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].ext.sid); - - expect(cygnusRequestUrl.search.r.imp[0].video.protocols).to.deep.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.protocols); - expect(cygnusRequestUrl.search.r.imp[0].video.maxduration).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.maxduration); - expect(cygnusRequestUrl.search.r.imp[0].video.minduration).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.minduration); - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].video.linearity).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.linearity); - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.deep.equal(DEFAULT_MIMES_MAP.HTML5); - expect(cygnusRequestUrl.search.r.imp[0].video.w).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.w); - expect(cygnusRequestUrl.search.r.imp[0].video.h).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.h); - }); - - it('when valid FLASH required bid request parameters are present', () => { - prebidRequest.bids[0].params.video.playerType = 'FLASH'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.protocol).to.equal(insecureExpectedUrl.protocol); - expect(cygnusRequestUrl.hostname).to.equal(insecureExpectedUrl.hostname); - expect(cygnusRequestUrl.port).to.equal(insecureExpectedUrl.port); - expect(cygnusRequestUrl.pathname).to.equal(insecureExpectedUrl.pathname); - - expect(cygnusRequestUrl.search.v).to.equal(insecureExpectedUrl.search.v); - expect(cygnusRequestUrl.search.s).to.equal(insecureExpectedUrl.search.s); - expect(cygnusRequestUrl.search.fn).to.equal(insecureExpectedUrl.search.fn); - expect(cygnusRequestUrl.search.r).to.exist; - - expect(cygnusRequestUrl.search.r.id).to.equal(prebidRequest.bidderRequestId); - - expect(cygnusRequestUrl.search.r.site.page).to.have.string(CYGNUS_REQUEST_R_PARAM.site.page); - - expect(cygnusRequestUrl.search.r.imp).to.be.a('array'); - expect(cygnusRequestUrl.search.r.imp[0]).to.have.all.keys(Object.keys(CYGNUS_REQUEST_R_PARAM.imp[0])); - - expect(cygnusRequestUrl.search.r.imp[0].id).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].id); - - expect(cygnusRequestUrl.search.r.imp[0].ext.siteID).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].ext.siteID); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].ext.sid); - - expect(cygnusRequestUrl.search.r.imp[0].video.protocols).to.deep.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.protocols); - expect(cygnusRequestUrl.search.r.imp[0].video.maxduration).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.maxduration); - expect(cygnusRequestUrl.search.r.imp[0].video.minduration).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.minduration); - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].video.linearity).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.linearity); - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.deep.equal(DEFAULT_MIMES_MAP.FLASH); - expect(cygnusRequestUrl.search.r.imp[0].video.w).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.w); - expect(cygnusRequestUrl.search.r.imp[0].video.h).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.h); - }); - - it('when required field site ID is a numeric string', () => { - prebidRequest.bids[0].params.video.siteID = '6'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.s).to.equal(insecureExpectedUrl.search.s); - expect(cygnusRequestUrl.search.r).to.exist; - - expect(cygnusRequestUrl.search.r.imp[0].ext.siteID).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].ext.siteID); - }); - - it('when required field maxduration is a numeric string', () => { - prebidRequest.bids[0].params.video.maxduration = '15'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.maxduration).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.maxduration); - }); - - describe('when optional field minduration', () => { - it('is valid number', () => { - prebidRequest.bids[0].params.video.minduration = 5; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.minduration).to.equal(prebidRequest.bids[0].params.video.minduration); - }); - - it('is valid number string', () => { - prebidRequest.bids[0].params.video.minduration = '5'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.minduration).to.equal(prebidRequest.bids[0].params.video.minduration); - }); - }); - - describe('when optional field startdelay', () => { - it('is valid string', () => { - prebidRequest.bids[0].params.video.startdelay = 'midroll'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(prebidRequest.bids[0].params.video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.have.string('m_1_1_s'); - }); - - it('is valid number string', () => { - prebidRequest.bids[0].params.video.startdelay = '5'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(prebidRequest.bids[0].params.video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.have.string('m_1_1_s'); - }); - - it('is valid midroll number', () => { - prebidRequest.bids[0].params.video.startdelay = 5; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(prebidRequest.bids[0].params.video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.have.string('m_1_1_s'); - }); - - it('is valid preroll number', () => { - prebidRequest.bids[0].params.video.startdelay = 0; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(prebidRequest.bids[0].params.video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.have.string('pr_1_1_s'); - }); - }); - - describe('when optional field linearity', () => { - it('is valid string', () => { - prebidRequest.bids[0].params.video.linearity = 'nonlinear'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.linearity).to.equal(2); - }); - }); - - describe('when optional field mimes', () => { - it('is valid mime', () => { - prebidRequest.bids[0].params.video.mimes = ['a/b']; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.deep.equal(prebidRequest.bids[0].params.video.mimes); - }); - }); - - describe('when optional field API list', () => { - it('is valid array', () => { - prebidRequest.bids[0].params.video.apiList = [2]; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.apiList).to.include.members([2]); - }); - }); - - describe('when optional field allowVPAID', () => { - it('is valid boolean', () => { - prebidRequest.bids[0].params.video.allowVPAID = true; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.include.members(DEFAULT_VPAID_MIMES_MAP.HTML5); - expect(cygnusRequestUrl.search.r.imp[0].video.apiList).to.include.members(SUPPORTED_API_MAP.HTML5); - }); - }); - }); - - describe('should make request with default values', () => { - describe('when optional field minduration', () => { - it('is invalid string', () => { - prebidRequest.bids[0].params.video.minduration = 'a'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.minduration).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.minduration); - }); - - it('is empty object', () => { - prebidRequest.bids[0].params.video.minduration = {}; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.minduration).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.minduration); - }); - - it('is empty array', () => { - prebidRequest.bids[0].params.video.minduration = []; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.minduration).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.minduration); - }); - - it('is undefined', () => { - prebidRequest.bids[0].params.video.minduration = undefined; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.minduration).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.minduration); - }); - }); - - describe('when optional field startdelay', () => { - it('is invalid string', () => { - prebidRequest.bids[0].params.video.startdelay = 'cucumber'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.have.string(CYGNUS_REQUEST_R_PARAM.imp[0].ext.sid); - }); - - it('is invalid number string', () => { - prebidRequest.bids[0].params.video.startdelay = '-5'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.have.string(CYGNUS_REQUEST_R_PARAM.imp[0].ext.sid); - }); - - it('is invalid number', () => { - prebidRequest.bids[0].params.video.startdelay = -5; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.have.string(CYGNUS_REQUEST_R_PARAM.imp[0].ext.sid); - }); - - it('is empty object', () => { - prebidRequest.bids[0].params.video.startdelay = {}; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.have.string(CYGNUS_REQUEST_R_PARAM.imp[0].ext.sid); - }); - - it('is empty array', () => { - prebidRequest.bids[0].params.video.startdelay = []; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.have.string(CYGNUS_REQUEST_R_PARAM.imp[0].ext.sid); - }); - - it('is undefined', () => { - prebidRequest.bids[0].params.video.startdelay = undefined; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.startdelay).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.startdelay); - expect(cygnusRequestUrl.search.r.imp[0].ext.sid).to.have.string(CYGNUS_REQUEST_R_PARAM.imp[0].ext.sid); - }); - }); - - describe('when optional field linearity', () => { - it('is invalid string', () => { - prebidRequest.bids[0].params.video.linearity = 'cucumber'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.linearity).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.linearity); - }); - - it('is empty object', () => { - prebidRequest.bids[0].params.video.linearity = {}; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.linearity).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.linearity); - }); - - it('is empty array', () => { - prebidRequest.bids[0].params.video.linearity = []; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.linearity).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.linearity); - }); - - it('is undefined', () => { - prebidRequest.bids[0].params.video.linearity = undefined; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.linearity).to.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.linearity); - }); - }); - - describe('when optional field mimes', () => { - it('is invalid mime string', () => { - prebidRequest.bids[0].params.video.mimes = 'a'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.deep.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.mimes); - }); - - it('is empty object', () => { - prebidRequest.bids[0].params.video.mimes = {}; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.deep.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.mimes); - }); - - it('is empty array', () => { - prebidRequest.bids[0].params.video.mimes = []; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.deep.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.mimes); - }); - - it('is undefined', () => { - prebidRequest.bids[0].params.video.mimes = undefined; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.deep.equal(CYGNUS_REQUEST_R_PARAM.imp[0].video.mimes); - }); - }); - - describe('when optional field API list', () => { - it('is invalid array', () => { - prebidRequest.bids[0].params.video.apiList = ['cucumber']; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.apiList).to.not.exist; - }); - - it('is empty array', () => { - prebidRequest.bids[0].params.video.apiList = []; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.apiList).to.not.exist; - }); - - it('is empty object', () => { - prebidRequest.bids[0].params.video.apiList = {}; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.apiList).to.not.exist; - }); - - it('is string', () => { - prebidRequest.bids[0].params.video.apiList = 'cucumber'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.apiList).to.not.exist; - }); - - it('is undefined', () => { - prebidRequest.bids[0].params.video.apiList = undefined; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.apiList).to.not.exist; - }); - }); - - describe('when optional field allowVPAID', () => { - it('is not boolean', () => { - prebidRequest.bids[0].params.video.allowVPAID = 'a'; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.have.members(CYGNUS_REQUEST_R_PARAM.imp[0].video.mimes); - expect(cygnusRequestUrl.search.r.imp[0].video.apiList).to.not.exist; - }); - - it('is empty object', () => { - prebidRequest.bids[0].params.video.allowVPAID = {}; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.have.members(CYGNUS_REQUEST_R_PARAM.imp[0].video.mimes); - expect(cygnusRequestUrl.search.r.imp[0].video.apiList).to.not.exist; - }); - - it('is empty array', () => { - prebidRequest.bids[0].params.video.allowVPAID = []; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.have.members(CYGNUS_REQUEST_R_PARAM.imp[0].video.mimes); - expect(cygnusRequestUrl.search.r.imp[0].video.apiList).to.not.exist; - }); - - it('is undefined', () => { - prebidRequest.bids[0].params.video.allowVPAID = undefined; - adapter.callBids(prebidRequest); - sinon.assert.calledOnce(adloader.loadScript); - let cygnusRequestUrl = url.parse(encodeURIComponent(adloader.loadScript.firstCall.args[0])); - cygnusRequestUrl.search.r = JSON.parse(decodeURIComponent(cygnusRequestUrl.search.r)); - - expect(cygnusRequestUrl.search.r.imp[0].video.mimes).to.have.members(CYGNUS_REQUEST_R_PARAM.imp[0].video.mimes); - expect(cygnusRequestUrl.search.r.imp[0].video.apiList).to.not.exist; - }); - }); - }) - - describe('should not make request', () => { - describe('when request', () => { - it('is empty', () => { - adapter.callBids({}); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is for no bids', () => { - adapter.callBids({ bids: [] }); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is undefined', () => { - adapter.callBids(undefined); - sinon.assert.notCalled(adloader.loadScript); - }); - }); - - describe('when request site ID', () => { - it('is negative number', () => { - prebidRequest.bids[0].params.video.siteID = -5; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is negative number string', () => { - prebidRequest.bids[0].params.video.siteID = '-5'; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is invalid string', () => { - prebidRequest.bids[0].params.video.siteID = 'cucumber'; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is undefined', () => { - prebidRequest.bids[0].params.video.siteID = undefined; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - }); - - describe('when request player type', () => { - it('is invalid string', () => { - prebidRequest.bids[0].params.video.playerType = 'cucumber'; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is number', () => { - prebidRequest.bids[0].params.video.playerType = 1; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is undefined', () => { - prebidRequest.bids[0].params.video.playerType = undefined; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - }); - - describe('when request protocols', () => { - it('is empty array', () => { - prebidRequest.bids[0].params.video.protocols = []; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is a string', () => { - prebidRequest.bids[0].params.video.protocols = 'cucumber'; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is an invalid array', () => { - prebidRequest.bids[0].params.video.protocols = ['cucumber']; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is undefined', () => { - prebidRequest.bids[0].params.video.protocols = undefined; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - }); - - describe('when request maxduration', () => { - it('is a non-numeric string', () => { - prebidRequest.bids[0].params.video.maxduration = 'cucumber'; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is a negative number', () => { - prebidRequest.bids[0].params.video.maxduration = -1; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is a negative number string', () => { - prebidRequest.bids[0].params.video.maxduration = '-1'; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - - it('is undefined', () => { - prebidRequest.bids[0].params.video.maxduration = undefined; - adapter.callBids(prebidRequest); - sinon.assert.notCalled(adloader.loadScript); - }); - }); - }); - }); - - describe('response from cygnus', () => { - let response; - let request; - let width; - let height; - - beforeEach(() => { - sinon.stub(bidmanager, 'addBidResponse'); - - [width, height] = PREBID_REQUEST.bids[0].sizes; - - request = JSON.parse(JSON.stringify(PREBID_REQUEST)); - response = JSON.parse(JSON.stringify(CYGNUS_RESPONSE)); - }); - - afterEach(() => { - bidmanager.addBidResponse.restore(); - }); - - describe('should add empty bid', () => { - describe('when response', () => { - beforeEach(() => { - adapter.callBids(request); - }); - - it('is empty object', () => { - $$PREBID_GLOBAL$$.handleCygnusResponse({}); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('code', PREBID_RESPONSE.code); - expect(response).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(response).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is empty array', () => { - $$PREBID_GLOBAL$$.handleCygnusResponse({}); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('code', PREBID_RESPONSE.code); - expect(response).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(response).to.have.property('code', PREBID_RESPONSE.code); - expect(response).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(response).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is undefined', () => { - $$PREBID_GLOBAL$$.handleCygnusResponse(undefined); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('code', PREBID_RESPONSE.code); - expect(response).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(response).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is number', () => { - $$PREBID_GLOBAL$$.handleCygnusResponse(1); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('code', PREBID_RESPONSE.code); - expect(response).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(response).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is string', () => { - $$PREBID_GLOBAL$$.handleCygnusResponse('cucumber'); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('code', PREBID_RESPONSE.code); - expect(response).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(response).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is explicit pass', () => { - $$PREBID_GLOBAL$$.handleCygnusResponse({ id: CYGNUS_RESPONSE.id }); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('code', PREBID_RESPONSE.code); - expect(response).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(response).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - }); - - describe('when impid', () => { - beforeEach(() => { - adapter.callBids(request); - }); - - it('is mismatched', () => { - response.seatbid[0].bid[0].impid = 'cucumber'; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is undefined', () => { - response.seatbid[0].bid[0].impid = undefined; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is array', () => { - response.seatbid[0].bid[0].impid = []; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is object', () => { - response.seatbid[0].bid[0].impid = {}; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is string', () => { - response.seatbid[0].bid[0].impid = {}; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - }); - - describe('when price level', () => { - beforeEach(() => { - adapter.callBids(request); - }); - - it('is string', () => { - response.seatbid[0].bid[0].ext.pricelevel = 'cucumber'; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is undefined', () => { - response.seatbid[0].bid[0].ext.pricelevel = undefined; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is array', () => { - response.seatbid[0].bid[0].ext.pricelevel = []; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is object', () => { - response.seatbid[0].bid[0].ext.pricelevel = {}; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - }); - - describe('when vasturl', () => { - beforeEach(() => { - adapter.callBids(request); - }); - - it('is undefined', () => { - response.seatbid[0].bid[0].ext.vasturl = undefined; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is number', () => { - response.seatbid[0].bid[0].ext.vasturl = 1; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - - it('is not a url', () => { - response.seatbid[0].bid[0].ext.vasturl = 'cucumber'; - $$PREBID_GLOBAL$$.handleCygnusResponse(response); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse).to.have.property('code', PREBID_RESPONSE.code); - expect(bidResponse).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(bidResponse).to.have.property('statusMessage', EMPTY_MESSAGE); - }); - }); - }); - - describe('should add available bid', () => { - describe('when response', () => { - beforeEach(() => { - adapter.callBids(request); - }); - - it('is success', () => { - $$PREBID_GLOBAL$$.handleCygnusResponse(CYGNUS_RESPONSE); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('code', PREBID_RESPONSE.code); - expect(response).to.have.property('bidderCode', PREBID_RESPONSE.bidderCode); - expect(response).to.have.property('statusMessage', PREBID_RESPONSE.statusMessage); - expect(response).to.have.property('cpm', PREBID_RESPONSE.cpm); - expect(response).to.have.property('vastUrl', PREBID_RESPONSE.vastUrl); - expect(response).to.have.property('descriptionUrl', PREBID_RESPONSE.descriptionUrl); - expect(response).to.have.property('width', PREBID_RESPONSE.width); - expect(response).to.have.property('height', PREBID_RESPONSE.height); - }); - }); - }); - }); -}); diff --git a/test/spec/modules/inneractiveBidAdapter_spec.js b/test/spec/modules/inneractiveBidAdapter_spec.js deleted file mode 100644 index aef9a6b7b49..00000000000 --- a/test/spec/modules/inneractiveBidAdapter_spec.js +++ /dev/null @@ -1,291 +0,0 @@ -/* globals context */ - -import {expect} from 'chai'; -import {default as InneractiveAdapter} from 'modules/inneractiveBidAdapter'; -import bidmanager from 'src/bidmanager'; - -// Using plain-old-style functions, why? see: http://mochajs.org/#arrow-functions -describe('InneractiveAdapter', function () { - let adapter, - bidRequest; - - beforeEach(function () { - adapter = new InneractiveAdapter(); - bidRequest = { - bidderCode: 'inneractive', - bids: [ - { - bidder: 'inneractive', - params: { - appId: '', - }, - placementCode: 'div-gpt-ad-1460505748561-0', - sizes: [[300, 250], [300, 600]], - bidId: '507e8db167d219', - bidderRequestId: '49acc957f92917', - requestId: '51381cd0-c29c-405b-9145-20f60abb1e76' - }, - { - bidder: 'inneractive', - params: { - noappId: '...', - }, - placementCode: 'div-gpt-ad-1460505661639-0', - sizes: [[728, 90], [970, 90]], - bidId: '507e8db167d220', - bidderRequestId: '49acc957f92917', - requestId: '51381cd0-c29c-405b-9145-20f60abb1e76' - }, - { - bidder: 'inneractive', - params: { - APP_ID: 'Inneractive_AndroidHelloWorld_Android', - spotType: 'rectangle', - customParams: { - Portal: 7002, - } - }, - placementCode: 'div-gpt-ad-1460505748561-0', - sizes: [[320, 50], [300, 600]], - bidId: '507e8db167d221', - bidderRequestId: '49acc957f92917', - requestId: '51381cd0-c29c-405b-9145-20f60abb1e76' - }, - { - bidder: 'inneractive', - params: { - appId: 'Inneractive_IosHelloWorld_iPhone', - spotType: 'banner', // Just for coverage considerations, no real impact in production - customParams: { - portal: 7001, - gender: '' - } - }, - placementCode: 'div-gpt-ad-1460505661639-0', - sizes: [[728, 90], [970, 90]], - bidId: '507e8db167d222', - bidderRequestId: '49acc957f92917', - requestId: '51381cd0-c29c-405b-9145-20f60abb1e76' - }] - }; - }); - - describe('Reporter', function () { - context('on HBPreBidError event', function () { - it('should contain "mbwError" the inside event report url', function () { - const Reporter = InneractiveAdapter._getUtils().Reporter; - const extraDetailsParam = { - 'appId': 'CrunchMind_DailyDisclosure_other', - 'spotType': 'rectangle', - 'portal': 7002 - }; - let eventReportUrl = Reporter.getEventUrl('HBPreBidError', extraDetailsParam); - expect(eventReportUrl).to.include('mbwError'); - }); - }); - }); - - it('should return an instance of this adapter having a "callBids" method', function () { - expect(adapter) - .to.be.instanceOf(InneractiveAdapter).and - .to.have.property('callBids').and - .to.be.a('function'); - }); - - describe('when sending out bid requests to the ad server', function () { - let bidRequests, - xhr; - - beforeEach(function () { - bidRequests = []; - xhr = sinon.useFakeXMLHttpRequest(); - xhr.onCreate = (request) => { - bidRequests.push(request); - }; - }); - - afterEach(function () { - xhr.restore(); - }); - - context('when there are no bid requests', function () { - it('should not issue a request', function () { - const Reporter = InneractiveAdapter._getUtils().Reporter; - Reporter.getEventUrl('HBPreBidError', { - 'appId': 'CrunchMind_DailyDisclosure_other', - 'spotType': 'rectangle', - 'portal': 7002 - }); - - delete bidRequest.bids; - adapter.callBids(bidRequest); - - expect(bidRequests).to.be.empty; - }); - }); - - context('when there is at least one bid request', function () { - it('should filter out invalid bids', function () { - const INVALID_BIDS_COUNT = 2; - sinon.spy(adapter, '_isValidRequest'); - adapter.callBids(bidRequest); - - for (let id = 0; id < INVALID_BIDS_COUNT; id++) { - expect(adapter._isValidRequest.getCall(id).returned(false)).to.be.true; - } - - adapter._isValidRequest.restore(); - }); - - it('should store all valid bids internally', function () { - adapter.callBids(bidRequest); - expect(Object.keys(adapter.bidByBidId).length).to.equal(2); - }); - - it('should issue ad requests to the ad server for every valid bid', function () { - adapter.callBids(bidRequest); - expect(bidRequests).to.have.lengthOf(2); - }); - }); - }); - - describe('when registering the bids that are returned with Prebid.js', function () { - const BID_DETAILS_ARG_INDEX = 1; - let server; - - beforeEach(function () { - sinon.stub(bidmanager, 'addBidResponse'); - server = sinon.fakeServer.create(); - }); - - afterEach(function () { - server.restore(); - bidmanager.addBidResponse.restore(); - }); - - context('when the bid is valid', function () { - let adServerResponse, - headers, - body; - - beforeEach(function () { - adServerResponse = { - headers: { - 'X-IA-Ad-Height': 250, - 'X-IA-Ad-Width': 300, - 'X-IA-Error': 'OK', - 'X-IA-Pricing': 'CPM', - 'X-IA-Pricing-Currency': 'USD', - 'X-IA-Pricing-Value': 0.0005 - }, - body: { - ad: { - html: '' - }, - config: { - tracking: { - impressions: [ - 'http://event.inner-active.mobi/simpleM2M/reportEvent?eventArchetype=impress…pe=3&network=Inneractive_CS&acp=&pcp=&secure=false&rtb=false&houseAd=false' - ], - clicks: [ - 'http://event.inner-active.mobi/simpleM2M/reportEvent?eventArchetype=richMed…pe=3&network=Inneractive_CS&acp=&pcp=&secure=false&rtb=false&houseAd=false', - '' - ], - passback: 'http://event.inner-active.mobi/simpleM2M/reportEvent?eventArchetype=passbac…pe=3&network=Inneractive_CS&acp=&pcp=&secure=false&rtb=false&houseAd=false' - }, - moat: { - countryCode: 'IL' - } - } - } - }; - headers = adServerResponse.headers; - body = JSON.stringify(adServerResponse.body); - }); - - it('should register bid responses with a status code of 1', function () { - server.respondWith([200, headers, body]); - adapter.callBids(bidRequest); - server.respond(); - - let firstRegisteredBidResponse = bidmanager.addBidResponse.firstCall.args[BID_DETAILS_ARG_INDEX]; - expect(firstRegisteredBidResponse) - .to.have.property('statusMessage', 'Bid available'); - }); - - it('should use the first element inside the bid request size array when no (width,height) is returned within the headers', function () { - delete headers['X-IA-Ad-Height']; - delete headers['X-IA-Ad-Width']; - server.respondWith([200, headers, body]); - adapter.callBids(bidRequest); - server.respond(); - - let firstRegisteredBidResponse = bidmanager.addBidResponse.firstCall.args[BID_DETAILS_ARG_INDEX]; - expect(firstRegisteredBidResponse).to.have.property('width', 320); - expect(firstRegisteredBidResponse).to.have.property('height', 50); - }); - }); - - context('when the bid is invalid', function () { - let passbackAdServerResponse, - headers, - body; - - beforeEach(function () { - passbackAdServerResponse = { - headers: { - 'X-IA-Error': 'House Ad', - 'X-IA-Content': 600145, - 'X-IA-Cid': 99999, - 'X-IA-Publisher': 206536, - 'Content-Type': 'application/json; charset=UTF-8', - 'X-IA-Session': 6512147119979250840, - 'X-IA-AdNetwork': 'inneractive360' - }, - body: { - 'ad': { - 'html': '' - }, - 'config': { - 'passback': 'http://event.inner-active.mobi/simpleM2M/reportEvent?eventArchetype=passbac…pe=3&network=Inneractive_CS&acp=&pcp=&secure=false&rtb=false&houseAd=false' - } - } - }; - headers = passbackAdServerResponse.headers; - body = JSON.stringify(passbackAdServerResponse.body); - }); - - it('should register bid responses with a status code of 2', function () { - server.respondWith([200, headers, body]); - adapter.callBids(bidRequest); - server.respond(); - - let firstRegisteredBidResponse = bidmanager.addBidResponse.firstCall.args[BID_DETAILS_ARG_INDEX]; - expect(firstRegisteredBidResponse) - .to.have.property('statusMessage', 'Bid returned empty or error response'); - }); - - it('should handle responses from our server in case we had no ad to offer', function () { - const n = bidRequest.bids.length; - bidRequest.bids[n - 1].params.appId = 'Komoona_InquisitrRectangle2_other'; - server.respondWith([200, headers, body]); - adapter.callBids(bidRequest); - server.respond(); - - let secondRegisteredBidResponse = bidmanager.addBidResponse.secondCall.args[BID_DETAILS_ARG_INDEX]; - expect(secondRegisteredBidResponse) - .to.have.property('statusMessage', 'Bid returned empty or error response'); - }); - - it('should handle JSON.parse errors', function () { - server.respondWith(''); - adapter.callBids(bidRequest); - server.respond(); - - const firstRegisteredBidResponse = bidmanager.addBidResponse.firstCall.args[BID_DETAILS_ARG_INDEX]; - expect(firstRegisteredBidResponse) - .to.have.property('statusMessage', 'Bid returned empty or error response'); - }); - }); - }); -}); diff --git a/test/spec/modules/innityBidAdapter_spec.js b/test/spec/modules/innityBidAdapter_spec.js index 7e4ac147c68..87042ca6a6d 100644 --- a/test/spec/modules/innityBidAdapter_spec.js +++ b/test/spec/modules/innityBidAdapter_spec.js @@ -1,157 +1,100 @@ -describe('innity adapter tests', function () { - var expect = require('chai').expect; - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - var adapter = require('modules/innityBidAdapter'); - var adLoader = require('src/adloader'); - var bidmanager = require('src/bidmanager'); - - var stubLoadScript; - - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - - afterEach(function () { - stubLoadScript.restore(); - }); - - describe('creation of bid url', function () { - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; - } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; - } - - it('bid request for single placement', function () { - var params = { - bids: [{ - placementCode: '/19968336/header-bid-tag-0', - sizes: [[300, 250]], - bidId: 'b12345', - bidder: 'innity', - params: { pub: '267', zone: '62546' } - }] - }; - - adapter().callBids(params); - - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledOnce(stubLoadScript); - - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); +import { expect } from 'chai'; +import { spec } from 'modules/innityBidAdapter'; + +describe('innityAdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with pub ID and zone ID param', () => { + expect(spec.isBidRequestValid({ + bidder: 'innity', + params: { + 'pub': 267, + 'zone': 62546 + }, + })).to.equal(true); + }); - expect(parsedBidUrlQueryString).to.have.property('pub').and.to.equal('267'); - expect(parsedBidUrlQueryString).to.have.property('zone').and.to.equal('62546'); - expect(parsedBidUrlQueryString).to.have.property('width').and.to.equal('300'); - expect(parsedBidUrlQueryString).to.have.property('height').and.to.equal('250'); + it('bidRequest with no required params', () => { + expect(spec.isBidRequestValid({ + bidder: 'innity', + params: { + }, + })).to.equal(false); }); }); - describe('handling bid response', function () { - it('should return complete bid response', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bids: [{ - placementCode: '/19968336/header-bid-tag-0', - sizes: [[300, 250]], - bidId: 'b12345', - bidder: 'innity', - params: { pub: '267', zone: '62546' } - }] - }; - - var response = { - cpm: 100, - width: 300, - height: 250, - callback_uid: 'b12345', - tag: '', + }, + headers: {} + }; + + it('result is correct', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0].requestId).to.equal('51ef8751f9aead'); + expect(result[0].cpm).to.equal(1); + expect(result[0].width).to.equal('300'); + expect(result[0].height).to.equal('250'); + expect(result[0].creativeId).to.equal('148186'); + expect(result[0].currency).to.equal('USD'); + expect(result[0].ttl).to.equal(60); + expect(result[0].ad).to.equal(''); }); }); }); diff --git a/test/spec/modules/inskinBidAdapter_spec.js b/test/spec/modules/inskinBidAdapter_spec.js new file mode 100644 index 00000000000..f22e6242d53 --- /dev/null +++ b/test/spec/modules/inskinBidAdapter_spec.js @@ -0,0 +1,259 @@ +import { expect } from 'chai'; +import { spec } from 'modules/inskinBidAdapter'; + +var bidFactory = require('src/bidfactory.js'); + +const ENDPOINT = 'https://mfad.inskinad.com/api/v2'; + +const REQUEST = { + 'bidderCode': 'inskin', + 'requestId': 'a4713c32-3762-4798-b342-4ab810ca770d', + 'bidderRequestId': '109f2a181342a9', + 'bidRequest': [{ + 'bidder': 'inskin', + 'params': { + 'networkId': 9874, + 'siteId': 730181 + }, + 'placementCode': 'div-gpt-ad-1487778092495-0', + 'sizes': [ + [728, 90], + [970, 90] + ], + 'bidId': '2b0f82502298c9', + 'bidderRequestId': '109f2a181342a9', + 'requestId': 'a4713c32-3762-4798-b342-4ab810ca770d' + }, + { + 'bidder': 'inskin', + 'params': { + 'networkId': 9874, + 'siteId': 730181 + }, + 'placementCode': 'div-gpt-ad-1487778092495-0', + 'sizes': [ + [728, 90], + [970, 90] + ], + 'bidId': '123', + 'bidderRequestId': '109f2a181342a9', + 'requestId': 'a4713c32-3762-4798-b342-4ab810ca770d' + }], + 'start': 1487883186070, + 'auctionStart': 1487883186069, + 'timeout': 3000 +}; + +const RESPONSE = { + 'headers': null, + 'body': { + 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, + 'decisions': { + '2b0f82502298c9': { + 'adId': 2364764, + 'creativeId': 1950991, + 'flightId': 2788300, + 'campaignId': 542982, + 'clickUrl': 'https://mfad.inskinad.com/r', + 'impressionUrl': 'https://mfad.inskinad.com/i.gif', + 'contents': [{ + 'type': 'html', + 'body': '', + 'data': { + 'height': 90, + 'width': 728, + 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', + 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' + }, + 'template': 'image' + }], + 'height': 90, + 'width': 728, + 'events': [], + 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5} + }, + '123': { + 'adId': 2364764, + 'creativeId': 1950991, + 'flightId': 2788300, + 'campaignId': 542982, + 'clickUrl': 'https://mfad.inskinad.com/r', + 'impressionUrl': 'https://mfad.inskinad.com/i.gif', + 'contents': [{ + 'type': 'html', + 'body': '', + 'data': { + 'height': 90, + 'width': 728, + 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', + 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' + }, + 'template': 'image' + }], + 'height': 90, + 'width': 728, + 'events': [], + 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5} + } + } + } +}; + +describe('InSkin BidAdapter', () => { + let bidRequests; + let adapter = spec; + + beforeEach(() => { + bidRequests = [ + { + bidder: 'inskin', + params: { + networkId: '9874', + siteId: 'xxxxx' + }, + placementCode: 'header-bid-tag-1', + sizes: [[300, 250], [300, 600]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + }); + + describe('bid request validation', () => { + it('should accept valid bid requests', () => { + let bid = { + bidder: 'inskin', + params: { + networkId: '9874', + siteId: 'xxxxx' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should accept valid bid requests with extra fields', () => { + let bid = { + bidder: 'inskin', + params: { + networkId: '9874', + siteId: 'xxxxx', + zoneId: 'xxxxx' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should reject bid requests without siteId', () => { + let bid = { + bidder: 'inskin', + params: { + networkId: '9874' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should reject bid requests without networkId', () => { + let bid = { + bidder: 'inskin', + params: { + siteId: '9874' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests validation', () => { + it('creates request data', () => { + let request = spec.buildRequests(bidRequests); + + expect(request).to.exist.and.to.be.a('object'); + }); + + it('request to inskin should contain a url', () => { + let request = spec.buildRequests(bidRequests); + + expect(request.url).to.have.string('inskinad.com'); + }); + + it('requires valid bids to make request', () => { + let request = spec.buildRequests([]); + expect(request.bidRequest).to.be.empty; + }); + + it('sends bid request to ENDPOINT via POST', () => { + let request = spec.buildRequests(bidRequests); + + expect(request.method).to.have.string('POST'); + }); + }); + describe('interpretResponse validation', () => { + it('response should have valid bidderCode', () => { + let bidRequest = spec.buildRequests(REQUEST.bidRequest); + let bid = bidFactory.createBid(1, bidRequest.bidRequest[0]); + + expect(bid.bidderCode).to.equal('inskin'); + }); + + it('response should include objects for all bids', () => { + let bids = spec.interpretResponse(RESPONSE, REQUEST); + + expect(bids.length).to.equal(2); + }); + + it('registers bids', () => { + let bids = spec.interpretResponse(RESPONSE, REQUEST); + bids.forEach(b => { + expect(b).to.have.property('cpm'); + expect(b.cpm).to.be.above(0); + expect(b).to.have.property('requestId'); + expect(b).to.have.property('cpm'); + expect(b).to.have.property('width'); + expect(b).to.have.property('height'); + expect(b).to.have.property('ad'); + expect(b).to.have.property('currency', 'USD'); + expect(b).to.have.property('creativeId'); + expect(b).to.have.property('ttl', 360); + expect(b).to.have.property('netRevenue', true); + expect(b).to.have.property('referrer'); + }); + }); + + it('handles nobid responses', () => { + let EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {'decisions': null}}) + let bids = spec.interpretResponse(EMPTY_RESP, REQUEST); + + expect(bids).to.be.empty; + }); + + it('handles no server response', () => { + let bids = spec.interpretResponse(null, REQUEST); + + expect(bids).to.be.empty; + }); + }); + describe('getUserSyncs', () => { + it('handles empty sync options', () => { + let opts = spec.getUserSyncs({}); + + expect(opts).to.be.empty; + }); + + it('should return two sync urls if pixel syncs are enabled', () => { + let syncOptions = {'pixelEnabled': true}; + let opts = spec.getUserSyncs(syncOptions); + + expect(opts.length).to.equal(2); + }); + + it('should return three sync urls if pixel and iframe syncs are enabled', () => { + let syncOptions = {'iframeEnabled': true, 'pixelEnabled': true}; + let opts = spec.getUserSyncs(syncOptions); + + expect(opts.length).to.equal(3); + }); + }); +}); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js new file mode 100644 index 00000000000..646448bd5a7 --- /dev/null +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -0,0 +1,249 @@ +import { expect } from 'chai'; +import { spec } from 'modules/invibesBidAdapter'; + +describe('invibesBidAdapter:', function () { + const BIDDER_CODE = 'invibes'; + const PLACEMENT_ID = '12345'; + const ENDPOINT = '//bid.videostep.com/Bid/VideoAdContent'; + const SYNC_ENDPOINT = '//k.r66net.com/GetUserSync'; + + let bidRequests = [ + { + bidId: 'b1', + bidder: BIDDER_CODE, + bidderRequestId: 'r1', + params: { + placementId: PLACEMENT_ID + }, + adUnitCode: 'test-div', + auctionId: 'a1', + sizes: [ + [300, 250], + [400, 300], + [125, 125] + ], + transactionId: 't1' + }, { + bidId: 'b2', + bidder: BIDDER_CODE, + bidderRequestId: 'r2', + params: { + placementId: 'abcde' + }, + adUnitCode: 'test-div', + auctionId: 'a2', + sizes: [ + [300, 250], + [400, 300] + ], + transactionId: 't2' + } + ]; + + beforeEach(function () { + top.window.invibes = null; + document.cookie = ''; + this.cStub1 = sinon.stub(console, 'info'); + }); + + afterEach(function () { + this.cStub1.restore(); + }); + + describe('isBidRequestValid:', function () { + context('valid bid request:', function () { + it('returns true when bidder params.placementId is set', function() { + const validBid = { + bidder: BIDDER_CODE, + params: { + placementId: PLACEMENT_ID + } + } + + expect(spec.isBidRequestValid(validBid)).to.be.true; + }) + }); + + context('invalid bid request:', function () { + it('returns false when no params', function () { + const invalidBid = { + bidder: BIDDER_CODE + } + + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('returns false when placementId is not set', function() { + const invalidBid = { + bidder: BIDDER_CODE, + params: { + id: '5' + } + } + + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + }); + + describe('buildRequests', () => { + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('sends cookies with the bid request', () => { + const request = spec.buildRequests(bidRequests); + expect(request.options.withCredentials).to.equal(true); + }); + + it('has location, html id, placement and width/height', () => { + const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + const parsedData = request.data; + expect(parsedData.location).to.exist; + expect(parsedData.videoAdHtmlId).to.exist; + expect(parsedData.vId).to.exist; + expect(parsedData.width).to.exist; + expect(parsedData.height).to.exist; + }); + + it('sends all Placement Ids', () => { + const request = spec.buildRequests(bidRequests); + expect(JSON.parse(request.data.bidParamsJson).placementIds).to.contain(bidRequests[0].params.placementId); + expect(JSON.parse(request.data.bidParamsJson).placementIds).to.contain(bidRequests[1].params.placementId); + }); + + it('uses cookies', () => { + global.document.cookie = 'ivNoCookie=1'; + let request = spec.buildRequests(bidRequests); + expect(request.data.lId).to.be.undefined; + }); + + it('does not overwrite the domain id', () => { + top.window.invibes = window.invibes || {}; + top.window.invibes.dom = {}; + let request = spec.buildRequests(bidRequests); + }); + + it('doesnt send the domain id if not graduated', () => { + global.document.cookie = 'ivbsdid={"id":"p4vauj.4ekt9w","hc":3,"temp":1}'; + let request = spec.buildRequests(bidRequests); + expect(request.data.lId).to.not.exist; + }); + + it('graduate and send the domain id', () => { + global.document.cookie = 'ivbsdid={"id":"p4rrk7.ax2i2s","hc":4,"temp":1}'; + let request = spec.buildRequests(bidRequests); + expect(request.data.lId).to.exist; + }); + + it('send the domain id if already graduated', () => { + global.document.cookie = 'ivbsdid={"id":"p4rrk7.ax2i2s"}'; + let request = spec.buildRequests(bidRequests); + expect(request.data.lId).to.exist; + }); + }) + + describe('interpretResponse', function () { + let response = { + Ads: [{ + BidPrice: 0.5, + VideoExposedId: 123 + }], + BidModel: { + BidVersion: 1, + PlacementId: '12345', + AuctionStartTime: Date.now(), + CreativeHtml: '' + } + }; + + let expectedResponse = [{ + requestId: bidRequests[0].bidId, + cpm: 0.5, + width: 400, + height: 300, + creativeId: 123, + currency: 'EUR', + netRevenue: true, + ttl: 300, + ad: ` + + + + + ` + }]; + + context('when the response is not valid', function () { + it('handles response with no bids requested', () => { + let emptyResult = spec.interpretResponse({ body: response }); + expect(emptyResult).to.be.empty; + }); + + it('handles empty response', () => { + let emptyResult = spec.interpretResponse(null, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + + it('handles response with bidding is not configured', () => { + let emptyResult = spec.interpretResponse({ body: { Ads: [{ BidPrice: 1 }] } }, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + + it('handles response with no ads are received', () => { + let emptyResult = spec.interpretResponse({ body: { BidModel: { PlacementId: '12345' }, AdReason: 'No ads' } }, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + + it('handles response with no ads are received - no ad reason', () => { + let emptyResult = spec.interpretResponse({ body: { BidModel: { PlacementId: '12345' } } }, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + + it('handles response when no placement Id matches', () => { + let emptyResult = spec.interpretResponse({ body: { BidModel: { PlacementId: '123456' }, Ads: [{ BidPrice: 1 }] } }, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + + it('handles response when placement Id is not present', () => { + let emptyResult = spec.interpretResponse({ BidModel: { }, Ads: [{ BidPrice: 1 }] }, { bidRequests }); + expect(emptyResult).to.be.empty; + }); + }); + + context('when the response is valid', function () { + it('responds with a valid bid', () => { + let result = spec.interpretResponse({ body: response }, { bidRequests }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('responds with a valid bid and uses logger', () => { + localStorage.InvibesDEBUG = true; + let result = spec.interpretResponse({ body: response }, { bidRequests }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('does not make multiple bids', () => { + localStorage.InvibesDEBUG = false; + let result = spec.interpretResponse({ body: response }, { bidRequests }); + let secondResult = spec.interpretResponse({ body: response }, { bidRequests }); + expect(secondResult).to.be.empty; + }); + }); + }); + + describe('getUserSyncs', function () { + it('returns an iframe if enabled', () => { + let response = spec.getUserSyncs({iframeEnabled: true}); + expect(response.type).to.equal('iframe'); + expect(response.url).to.equal(SYNC_ENDPOINT); + }); + + it('returns undefined if iframe not enabled ', () => { + let response = spec.getUserSyncs({ iframeEnabled: false }); + expect(response).to.equal(undefined); + }); + }); +}); diff --git a/test/spec/modules/iqmBidAdapter_spec.js b/test/spec/modules/iqmBidAdapter_spec.js new file mode 100644 index 00000000000..8958a4dfc45 --- /dev/null +++ b/test/spec/modules/iqmBidAdapter_spec.js @@ -0,0 +1,219 @@ +import {expect} from 'chai'; +import {spec} from 'modules/iqmBidAdapter' +import * as utils from 'src/utils'; + +describe('iqmBidAdapter', () => { + const ENDPOINT_URL = 'https://pbd.bids.iqm.com'; + const bidRequests = [{ + bidder: 'iqm', + params: { + position: 1, + tagId: 'tagId-1', + placementId: 'placementId-1', + pubId: 'pubId-1', + secure: true, + bidfloor: 0.5 + }, + placementCode: 'pcode000', + transactionId: 'tx000', + sizes: [[300, 250]], + bidId: 'bid000', + bidderRequestId: '117d765b87bed38', + requestId: 'req000' + }]; + + const bidResponses = { + body: { + id: 'req000', + seatbid: [{ + bid: [{ + nurl: 'nurl', + adm: '', + crid: 'cr-65981', + impid: 'bid000', + price: 0.99, + w: 300, + h: 250, + adomain: ['https://example.com'], + id: 'bid000', + ttl: 300 + }] + }] + }, + headers: {}}; + + const bidResponseEmptySeat = { + body: { + id: 'req000', + seatbid: [] + }, + headers: {} + }; + + const bidResponseEmptyBid = { + body: { + id: 'req000', + seatbid: [{ + bid: [] + }] + }, + headers: {} + }; + + const bidResponseNoImpId = { + body: { + id: 'req000', + seatbid: [{ + bid: [{ + nurl: 'nurl', + adm: '', + crid: 'cr-65981', + price: 0.99, + w: 300, + h: 250, + adomain: ['https://example.com'], + id: 'bid000', + ttl: 300 + }] + }] + }, + headers: {} + }; + + describe('Request verification', () => { + it('basic property verification', () => { + expect(spec.code).to.equal('iqm'); + expect(spec.aliases).to.be.an('array'); + // expect(spec.aliases).to.be.ofSize(1); + expect(spec.aliases).to.have.lengthOf(1); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'iqm', + 'params': { + 'placementId': 'placementId', + 'tagId': 'tagId', + 'publisherId': 'pubId' + }, + 'adUnitCode': 'ad-unit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }; + + it('should return false for empty object', () => { + expect(spec.isBidRequestValid({})).to.equal(false); + }); + + it('should return false for request without param', () => { + let bid = Object.assign({}, bid); + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false for invalid params', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 'placementId' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true for proper request', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('buildRequests', () => { + it('sends every bid request to ENDPOINT_URL via POST method', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(ENDPOINT_URL); + // expect(requests[1].method).to.equal('POST'); + // expect(requests[1].url).to.equal(ENDPOINT_URL); + }); + + it('should send request data with every request', () => { + const requests = spec.buildRequests(bidRequests); + const data = requests[0].data; + expect(data.id).to.equal(bidRequests[0].requestId); + + expect(data.imp.id).to.equal(bidRequests[0].bidId); + expect(data.imp.bidfloor).to.equal(bidRequests[0].params.bidfloor); + expect(data.imp.secure).to.equal(1); + expect(data.imp.displaymanager).to.equal('Prebid.js'); + expect(data.imp.displaymanagerver).to.equal('v.1.0.0'); + expect(data.imp.mediatype).to.equal('banner'); + expect(data.imp.banner).to.deep.equal({ + w: 300, + h: 250 + }); + expect(data.publisherId).to.equal(utils.getBidIdParameter('publisherId', bidRequests[0].params)); + expect(data.tagId).to.equal(utils.getBidIdParameter('tagId', bidRequests[0].params)); + expect(data.placementId).to.equal(utils.getBidIdParameter('placementId', bidRequests[0].params)); + expect(data.device.w).to.equal(screen.width); + expect(data.device.h).to.equal(screen.height); + expect(data.device.make).to.equal(navigator.vendor ? navigator.vendor : ''); + expect(data.device.ua).to.equal(navigator.userAgent); + expect(data.device.dnt).to.equal(navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNotTrack === '1' || navigator.doNotTrack === 'yes' ? 1 : 0); + expect(data.site).to.deep.equal({ + id: utils.getBidIdParameter('tagId', bidRequests[0].params), + page: utils.getTopWindowLocation().href, + domain: utils.getTopWindowLocation().host + }); + + expect(data.device.ua).to.equal(navigator.userAgent); + expect(data.device.h).to.equal(screen.height); + expect(data.device.w).to.equal(screen.width); + + expect(data.site.id).to.equal(bidRequests[0].params.tagId); + expect(data.site.page).to.equal(utils.getTopWindowLocation().href); + expect(data.site.domain).to.equal(utils.getTopWindowLocation().host); + }); + }); + + describe('interpretResponse', () => { + it('should handle no bid response', () => { + const response = spec.interpretResponse({ body: null }, { bidRequests }); + expect(response.length).to.equal(0); + }); + + it('should have at least one Seat Object', () => { + const request = spec.buildRequests(bidRequests); + const response = spec.interpretResponse(bidResponseEmptySeat, request); + expect(response.length).to.equal(0); + }); + + it('should have at least one Bid Object', () => { + const request = spec.buildRequests(bidRequests); + const response = spec.interpretResponse(bidResponseEmptyBid, request); + expect(response.length).to.equal(0); + }); + + it('should have impId in Bid Object', () => { + const request = spec.buildRequests(bidRequests); + const response = spec.interpretResponse(bidResponseNoImpId, request); + expect(response.length).to.equal(0); + }); + + it('should handle valid response', () => { + const request = spec.buildRequests(bidRequests); + const response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').to.have.lengthOf(1); + + let bid = response[0]; + expect(bid).to.have.property('requestId', 'bid000'); + expect(bid).to.have.property('currency', 'USD'); + expect(bid).to.have.property('cpm', 0.99); + expect(bid).to.have.property('creativeId', 'cr-65981'); + expect(bid).to.have.property('width', 300); + expect(bid).to.have.property('height', 250); + expect(bid).to.have.property('ttl', 300); + expect(bid).to.have.property('ad', ''); + }); + }); + }); +}); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js new file mode 100644 index 00000000000..0e00a64aab4 --- /dev/null +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -0,0 +1,412 @@ +import * as utils from 'src/utils'; +import { config } from 'src/config'; +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/ixBidAdapter'; + +describe('IndexexchangeAdapter', () => { + const IX_ENDPOINT = 'http://as.casalemedia.com/cygnus'; + const BIDDER_VERSION = 7.2; + + const DEFAULT_BANNER_VALID_BID = [ + { + bidder: 'ix', + params: { + siteId: '123', + size: [300, 250] + }, + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', + bidId: '1a2b3c4d', + bidderRequestId: '11a22b33c44d', + auctionId: '1aa2bb3cc4dd' + } + ]; + const DEFAULT_BANNER_BID_RESPONSE = { + cur: 'USD', + id: '11a22b33c44d', + seatbid: [ + { + bid: [ + { + crid: '12345', + adomain: ['www.abc.com'], + adid: '14851455', + impid: '1a2b3c4d', + cid: '3051266', + price: 100, + w: 300, + h: 250, + id: '1', + ext: { + dspid: 50, + pricelevel: '_100', + advbrandid: 303325, + advbrand: 'OECTA' + }, + adm: '' + } + ], + seat: '3970' + } + ] + }; + + describe('inherited functions', () => { + it('should exists and is a function', () => { + const adapter = newBidder(spec); + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when required params found for a banner ad', () => { + expect(spec.isBidRequestValid(DEFAULT_BANNER_VALID_BID[0])).to.equal(true); + }); + + it('should return true when optional params found for a banner ad', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = 50; + bid.params.bidFloorCur = 'USD'; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when siteID is number', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.siteId = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when siteID is missing', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + delete bid.params.siteId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when size is missing', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + delete bid.params.size; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when size array is wrong length', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.size = [ + 300, + 250, + 250 + ]; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when size array is array of strings', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.size = ['300', '250']; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when there is only bidFloor', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = 50; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when there is only bidFloorCur', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloorCur = 'USD'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when bidFloor is string', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = '50'; + bid.params.bidFloorCur = 'USD'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when bidFloorCur is number', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = 50; + bid.params.bidFloorCur = 70; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequestsBanner', () => { + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const requestUrl = request.url; + const requestMethod = request.method; + const query = request.data; + + it('request should be made to IX endpoint with GET method', () => { + expect(requestMethod).to.equal('GET'); + expect(requestUrl).to.equal(IX_ENDPOINT); + }); + + it('query object (version, siteID and request) should be correct', () => { + expect(query.v).to.equal(BIDDER_VERSION); + expect(query.s).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); + expect(query.r).to.exist; + expect(query.ac).to.equal('j'); + expect(query.sd).to.equal(1); + }); + + it('payload should have correct format and value', () => { + const payload = JSON.parse(query.r); + + 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.ext).to.exist; + expect(payload.ext.source).to.equal('prebid'); + expect(payload.imp).to.exist; + expect(payload.imp).to.be.an('array'); + expect(payload.imp).to.have.lengthOf(1); + }); + + it('impression should have correct format and value', () => { + const impression = JSON.parse(query.r).imp[0]; + const sidValue = `${DEFAULT_BANNER_VALID_BID[0].params.size[0].toString()}x${DEFAULT_BANNER_VALID_BID[0].params.size[1].toString()}`; + + expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); + expect(impression.banner).to.exist; + expect(impression.banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); + expect(impression.banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); + expect(impression.banner.topframe).to.exist; + expect(impression.banner.topframe).to.be.oneOf([0, 1]); + expect(impression.ext).to.exist; + expect(impression.ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(impression.ext.sid).to.equal(sidValue); + }); + + it('impression should have bidFloor and bidFloorCur if configured', () => { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = 50; + bid.params.bidFloorCur = 'USD'; + const requestBidFloor = spec.buildRequests([bid]); + const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + + expect(impression.bidfloor).to.equal(bid.params.bidFloor); + expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); + }); + + it('should add first party data to page url in bid request if it exists in config', () => { + config.setConfig({ + ix: { + firstPartyData: { + ab: 123, + cd: '123#ab', + 'e/f': 456, + 'h?g': '456#cd' + } + } + }); + + const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + 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`; + + expect(pageUrl).to.equal(expectedPageUrl); + }); + + it('should not set first party data if it is not an object', () => { + config.setConfig({ + ix: { + firstPartyData: 500 + } + }); + + const requestFirstPartyDataNumber = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const pageUrl = JSON.parse(requestFirstPartyDataNumber.data.r).site.page; + + expect(pageUrl).to.equal(utils.getTopWindowUrl()); + }); + + it('should not set first party or timeout if it is not present', () => { + config.setConfig({ + ix: {} + }); + + const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; + + expect(pageUrl).to.equal(utils.getTopWindowUrl()); + expect(requestWithoutConfig.data.t).to.be.undefined; + }); + + it('should not set first party or timeout if it is setConfig is not called', () => { + const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; + + expect(pageUrl).to.equal(utils.getTopWindowUrl()); + expect(requestWithoutConfig.data.t).to.be.undefined; + }); + + it('should set timeout if publisher set it through setConfig', () => { + config.setConfig({ + ix: { + timeout: 500 + } + }); + const requestWithTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + + expect(requestWithTimeout.data.t).to.equal(500); + }); + + it('should set timeout if timeout is a string', () => { + config.setConfig({ + ix: { + timeout: '500' + } + }); + const requestStringTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + + expect(requestStringTimeout.data.t).to.be.undefined; + }); + }); + + describe('interpretResponseBanner', () => { + it('should get correct bid response', () => { + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + ad: '', + currency: 'USD', + ttl: 60, + netRevenue: true, + dealId: undefined + } + ]; + const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set creativeId to default value if not provided', () => { + const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + delete bidResponse.seatbid[0].bid[0].crid; + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '-', + width: 300, + height: 250, + ad: '', + currency: 'USD', + ttl: 60, + netRevenue: true, + dealId: undefined + } + ]; + const result = spec.interpretResponse({ body: bidResponse }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set Japanese price correctly', () => { + const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + bidResponse.cur = 'JPY'; + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 100, + creativeId: '12345', + width: 300, + height: 250, + ad: '', + currency: 'JPY', + ttl: 60, + netRevenue: true, + dealId: undefined + } + ]; + const result = spec.interpretResponse({ body: bidResponse }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set dealId correctly', () => { + const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + bidResponse.seatbid[0].bid[0].ext.dealid = 'deal'; + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + ad: '', + currency: 'USD', + ttl: 60, + netRevenue: true, + dealId: 'deal' + } + ]; + const result = spec.interpretResponse({ body: bidResponse }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('bidrequest should have consent info if gdprApplies and consentString exist', () => { + const options = { + gdprConsent: { + gdprApplies: true, + consentString: '3huaa11=qu3198ae', + vendorData: {} + } + }; + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent.data.r); + + expect(requestWithConsent.regs.ext.gdpr).to.equal(1); + expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); + }); + + it('bidrequest should not have consent field if consentString is undefined', () => { + const options = { + gdprConsent: { + gdprApplies: true, + vendorData: {} + } + }; + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent.data.r); + + expect(requestWithConsent.regs.ext.gdpr).to.equal(1); + expect(requestWithConsent.user).to.be.undefined; + }); + + it('bidrequest should not have gdpr field if gdprApplies is undefined', () => { + const options = { + gdprConsent: { + consentString: '3huaa11=qu3198ae', + vendorData: {} + } + }; + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent.data.r); + + expect(requestWithConsent.regs).to.be.undefined; + expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); + }); + + it('bidrequest should not have consent info if options.gdprConsent is undefined', () => { + const options = {}; + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent.data.r); + + expect(requestWithConsent.regs).to.be.undefined; + expect(requestWithConsent.user).to.be.undefined; + }); + }); +}); diff --git a/test/spec/modules/jcmBidAdapter_spec.js b/test/spec/modules/jcmBidAdapter_spec.js index b34141869d8..27784def4f9 100644 --- a/test/spec/modules/jcmBidAdapter_spec.js +++ b/test/spec/modules/jcmBidAdapter_spec.js @@ -1,244 +1,139 @@ -describe('jcm adapter tests', function () { - var expect = require('chai').expect; - var urlParse = require('url-parse'); +import { expect } from 'chai'; +import { spec } from 'modules/jcmBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; - // FYI: querystringify will perform encoding/decoding - var querystringify = require('querystringify'); +const ENDPOINT = '//media.adfrontiers.com/'; - var adapter = require('modules/jcmBidAdapter'); - var adLoader = require('src/adloader'); - var bidmanager = require('src/bidmanager'); +describe('jcmAdapter', () => { + const adapter = newBidder(spec); - let stubLoadScript; - - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); }); - afterEach(function () { - stubLoadScript.restore(); - }); + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'jcm', + 'params': { + 'siteId': '3608' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; - describe('creation of bid url', function () { - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; - } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; - } - if (typeof ($$PREBID_GLOBAL$$._adsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._adsReceived = []; - } - - it('should be called only once', function () { - var params = { - bidderCode: 'jcm', - bidder: 'jcm', - bids: [ - { - bidId: '3c9408cdbf2f68', - sizes: [[300, 250], [300, 300]], - bidder: 'jcm', - params: { siteId: '3608', adSizes: '300x250' }, - requestId: '10b327aa396609', - placementCode: '/19968336/header-bid-tag-0' - } - - ] - }; - - adapter().callBids(params); - - sinon.assert.calledOnce(stubLoadScript); + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should fix parameter name', function () { - var params = { - bidderCode: 'jcm', - bidder: 'jcm', - bids: [ - { - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'jcm', - params: { siteId: '3608', adSizes: '300x250' }, - requestId: '10b327aa396609', - placementCode: '/19968336/header-bid-tag-0' - } - - ] - }; - - adapter().callBids(params); - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledWith(stubLoadScript, bidUrl); - - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); - expect(parsedBidUrl.hostname).to.equal('media.adfrontiers.com'); - expect(parsedBidUrl.pathname).to.equal('/pq'); + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'jcm', + 'params': { + 'siteId': '3608' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } - expect(parsedBidUrlQueryString).to.have.property('t').and.to.equal('hb'); - expect(parsedBidUrlQueryString).to.have.property('bids'); + ]; - var bidObjArr = JSON.parse(parsedBidUrlQueryString.bids); - expect(bidObjArr).to.have.property('bids'); - var bidObj = bidObjArr.bids[0]; + const request = spec.buildRequests(bidRequests); - expect(bidObj).to.have.property('adSizes').and.to.equal('300x250'); - expect(bidObj).to.have.property('siteId').and.to.equal('3608'); - expect(bidObj).to.have.property('callbackId').and.to.equal('3c9408cdbf2f68'); + it('sends bid request to ENDPOINT via GET', () => { + expect(request.method).to.equal('GET'); }); - }); - describe('placement by size', function () { - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; - } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; - } - if (typeof ($$PREBID_GLOBAL$$._adsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._adsReceived = []; - } - - it('should be called with specific parameters for two bids', function () { - var params = { - bidderCode: 'jcm', - bidder: 'jcm', - bids: [ - { - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'jcm', - params: { siteId: '3608', adSizes: '300x250' }, - requestId: '10b327aa396609', - placementCode: '/19968336/header-bid-tag-0' - }, - { - bidId: '3c9408cdbf2f69', - sizes: [[728, 90]], - bidder: 'jcm', - params: { siteId: '3608', adSizes: '728x90' }, - requestId: '10b327aa396610', - placementCode: '/19968336/header-bid-tag-1' - } - - ] - }; - - adapter().callBids(params); - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledWith(stubLoadScript, bidUrl); - - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - expect(parsedBidUrl.hostname).to.equal('media.adfrontiers.com'); - expect(parsedBidUrl.pathname).to.equal('/pq'); - - expect(parsedBidUrlQueryString).to.have.property('t').and.to.equal('hb'); - expect(parsedBidUrlQueryString).to.have.property('bids'); - - var bidObjArr = JSON.parse(parsedBidUrlQueryString.bids); - expect(bidObjArr).to.have.property('bids'); - var bidObj1 = bidObjArr.bids[0]; - - expect(bidObj1).to.have.property('adSizes').and.to.equal('300x250'); - expect(bidObj1).to.have.property('siteId').and.to.equal('3608'); - expect(bidObj1).to.have.property('callbackId').and.to.equal('3c9408cdbf2f68'); - - var bidObj2 = bidObjArr.bids[1]; - - expect(bidObj2).to.have.property('adSizes').and.to.equal('728x90'); - expect(bidObj2).to.have.property('siteId').and.to.equal('3608'); - expect(bidObj2).to.have.property('callbackId').and.to.equal('3c9408cdbf2f69'); + it('sends correct bid parameters', () => { + const payloadArr = request.data.split('&'); + expect(request.method).to.equal('GET'); + expect(payloadArr.length).to.equal(4); + expect(payloadArr[0]).to.equal('t=hb'); + expect(payloadArr[1]).to.equal('ver=1.0'); + expect(payloadArr[2]).to.equal('compact=true'); + const adReqStr = request.data.split('&bids=')[1]; + const adReq = JSON.parse(decodeURIComponent(adReqStr)); + const adReqBid = JSON.parse(decodeURIComponent(adReqStr)).bids[0]; + expect(adReqBid.siteId).to.equal('3608'); + expect(adReqBid.callbackId).to.equal('30b31c1838de1e'); + expect(adReqBid.adSizes).to.equal('300x250,300x600'); }); }); - describe('handling of the callback response', function () { - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; - } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; - } - if (typeof ($$PREBID_GLOBAL$$._adsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._adsReceived = []; - } - - var params = { - bidderCode: 'jcm', - bidder: 'jcm', - bidderRequestId: '2068db3c904101', - bids: [ - { - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'jcm', - params: { siteId: '3608', adSizes: '300x250' }, - requestId: '10b327aa396609', - placementCode: '/19968336/header-bid-tag-0' - }, + describe('interpretResponse', () => { + it('should get correct bid response', () => { + let serverResponse = {'bids': [{'width': 300, 'height': 250, 'creativeId': '29681110', 'ad': '', 'cpm': 0.5, 'callbackId': '30b31c1838de1e'}]}; + + let expectedResponse = [ { - bidId: '3c9408cdbf2f69', - sizes: [[728, 90]], - bidder: 'jcm', - params: { siteId: '3608', adSizes: '728x90' }, - requestId: '10b327aa396610', - placementCode: '/19968336/header-bid-tag-1' + 'requestId': '30b31c1838de1e', + 'bidderCode': 'jcm', + 'cpm': 0.5, + 'creativeId': '29681110', + 'width': 300, + 'height': 250, + 'ttl': 60, + 'currency': 'USA', + 'netRevenue': true, + 'ad': '', } - - ] - }; - - var response = '{"bids":[{"width":300,"cpm":3,"ad":"%3Cimg+src%3D%22http%3A%2F%2Fmedia.adfrontiers.com%2Fimgs%2Fpartnership_300x250.png%22%3E","callbackId":"3c9408cdbf2f68","height":250},{"width":728,"cpm":0,"ad":"%3Cimg+src%3D%22http%3A%2F%2Fmedia.adfrontiers.com%2Fimgs%2Fpartnership_728x90.png%22%3E","callbackId":"3c9408cdbf2f69","height":90}]}'; - - it('callback function should exist', function () { - expect(pbjs.processJCMResponse).to.exist.and.to.be.a('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]).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('bidmanager.addBidResponse should be called twice with correct arguments', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - adapter().callBids(params); + it('handles nobid responses', () => { + let serverResponse = {'bids': []}; - var adUnits = new Array(); - var unit = new Object(); - unit.bids = [params]; - unit.code = '/19968336/header-bid-tag'; - unit.sizes = [[300, 250], [728, 90]]; - adUnits.push(unit); + let result = spec.interpretResponse({ body: serverResponse }); + expect(result.length).to.equal(0); + }); + }); + describe('getUserSyncs', () => { + it('Verifies sync iframe option', () => { + expect(spec.getUserSyncs({})).to.be.undefined; + expect(spec.getUserSyncs({ iframeEnabled: false })).to.be.undefined; + 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('//media.adfrontiers.com/hb/jcm_usersync.html'); + }); - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - $$PREBID_GLOBAL$$.adUnits = adUnits; - pbjs.processJCMResponse(response); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - var bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0]; - var bidObject2 = stubAddBidResponse.getCall(1).args[1]; - - expect(bidPlacementCode1).to.equal('/19968336/header-bid-tag-0'); - expect(bidObject1.cpm).to.equal(3); - expect(bidObject1.ad).to.equal(''); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.height).to.equal(250); - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.bidderCode).to.equal('jcm'); - - expect(bidPlacementCode2).to.equal('/19968336/header-bid-tag-1'); - expect(bidObject2.getStatusCode()).to.equal(2); - - sinon.assert.calledTwice(stubAddBidResponse); - stubAddBidResponse.restore(); + it('Verifies sync image option', () => { + expect(spec.getUserSyncs({ image: false })).to.be.undefined; + const options = spec.getUserSyncs({ image: true }); + 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('//media.adfrontiers.com/hb/jcm_usersync.png'); }); }); }); diff --git a/test/spec/modules/justpremiumBidAdapter_spec.js b/test/spec/modules/justpremiumBidAdapter_spec.js index 8416d662572..226a5788cef 100644 --- a/test/spec/modules/justpremiumBidAdapter_spec.js +++ b/test/spec/modules/justpremiumBidAdapter_spec.js @@ -1,386 +1,127 @@ -import { expect, assert } from 'chai'; -import Adapter from 'modules/justpremiumBidAdapter'; -import bidmanager from 'src/bidmanager'; -import adLoader from 'src/adloader'; -import PREBID_CONSTANTS from 'src/constants.json'; -import * as utils from 'src/utils'; - -const CONST = { - COOKIE: '//ox-d.justpremium.com/w/1.0/cj', - LIB: '//cdn-cf.justpremium.com/js/jpx.js' -}; - -function createCookie(name, value, days) { - var expires = ''; - if (days) { - var date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - expires = '; expires=' + date.toUTCString(); - } - document.cookie = name + '=' + value + expires + '; path=/'; -} +import { expect } from 'chai' +import { spec } from 'modules/justpremiumBidAdapter' describe('justpremium adapter', () => { - describe('setup', () => { - let sandbox; - let factory; - - beforeEach(() => { - factory = { - JAM: { - instance: () => { - } - } - }; - sandbox = sinon.sandbox.create(); - sandbox.stub(adLoader, 'loadScript', (url, callback) => { - if (url === window.location.protocol + CONST.LIB) { - window.Jpx = factory; - callback(); - } - }); - sandbox.stub(factory.JAM, 'instance', () => { - return {}; - }) - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('callBids should exists and be a function', () => { - const adapter = new Adapter(); - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - - it('should request script to sync cookies and download jpx ad manager lib', () => { - const adapter = new Adapter(); - const _request = adLoader.loadScript; - const _instance = factory.JAM.instance; - - adapter.callBids({}); - - assert(_request.calledTwice); - assert.lengthOf(_request.args[0], 1); - assert.lengthOf(_request.args[1], 2); - assert.equal(_request.args[0][0], window.location.protocol + CONST.COOKIE); - assert.equal(_request.args[1][0], window.location.protocol + CONST.LIB); - expect(_request.args[1][1]).to.exist.and.to.be.a('function'); - assert(_instance.calledOnce); - }); - - it('should request proper version of jpx ad manager', () => { - createCookie('jpxhbjs', 'v2.0.0', 1); - - const adapter = new Adapter(); - const _request = adLoader.loadScript; - const parts = CONST.LIB.split('/'); - parts.splice(parts.length - 1, 0, 'v2.0.0'); - - adapter.callBids({}); - - assert.equal(_request.args[1][0], window.location.protocol + parts.join('/')); - expect(_request.args[1][1]).to.exist.and.to.be.a('function'); - - createCookie('jpxhbjs', '', 0); - }); - }); - - describe('callBids', () => { - let sandbox; - let adapter; - let jPAM; - let bidder; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - window.top.jPAM = jPAM = { - initialized: true, - cmd: [], - cb: { - bidder20000: { - createBid: () => { - } - } + let adUnits = [ + { + bidder: 'justpremium', + params: { + zone: 28313, + allow: ['lb', 'wp'] + } + }, + { + bidder: 'justpremium', + params: { + zone: 32831, + exclude: ['sa'] + } + }, + ] + + describe('isBidRequestValid', () => { + it('Verifies bidder code', () => { + expect(spec.code).to.equal('justpremium') + }) + + it('Verify build request', () => { + expect(spec.isBidRequestValid({bidder: 'justpremium', params: {}})).to.equal(false) + expect(spec.isBidRequestValid({})).to.equal(false) + expect(spec.isBidRequestValid(adUnits[0])).to.equal(true) + expect(spec.isBidRequestValid(adUnits[1])).to.equal(true) + }) + }) + + describe('buildRequests', () => { + it('Verify build request and parameters', () => { + const request = spec.buildRequests(adUnits) + 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(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) + expect(jpxRequest.wh).to.equal(window.top.innerHeight) + expect(jpxRequest.c).to.not.equal('undefined') + expect(jpxRequest.id).to.equal(adUnits[0].params.zone) + expect(jpxRequest.sizes).to.not.equal('undefined') + }) + }) + + describe('interpretResponse', () => { + const request = spec.buildRequests(adUnits) + it('Verify server response', () => { + let response = { + 'bid': { + '28313': [{ + 'id': 3213123, + 'height': 250, + 'width': 970, + 'price': 0.52, + 'format': 'lb', + 'adm': 'creative code' + }] }, - listeners: {}, - hasPlugin: () => { - return true; + 'pass': { + '28313': false }, - getPlugin: () => { - return bidder; - } - }; - sandbox.stub(adLoader, 'loadScript'); - sandbox.spy(jPAM, 'hasPlugin'); - adapter = new Adapter(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should detect that jpx manager is ready', () => { - const _request = adLoader.loadScript; - const _check = jPAM.hasPlugin; - - adapter.callBids({}); - - expect(_check.callCount).to.be.equal(1); - assert(_request.calledOnce); - }); - - it('should throw an error', () => { - const _request = adLoader.loadScript; - const req = { - bidderCode: 'justpremium', - bids: [ - { - bidId: 'bidId1', - bidder: 'justpremium', - params: {}, - sizes: [[1, 1]], - placementCode: 'div-gpt-ad-1471513102552-1' - } - ] - }; - - try { - adapter.callBids(req); - } catch (e) { - assert.instanceOf(e, Error); + 'deals': {} } - assert(_request.calledOnce); - }); - - it('should request bids and send proper arguments', () => { - const _request = adLoader.loadScript; - const req = { - bidderCode: 'justpremium', - bids: [ - { - bidId: 'bidId1', - bidder: 'justpremium', - params: { - zone: 20000 - }, - sizes: [[1, 1]], - placementCode: 'div-gpt-ad-1471513102552-1' - } - ] - }; - - adapter.callBids(req); - const params = {}; - _request.getCall(1).args[0].split('?').pop().split('&').forEach(keypair => { - const kp = keypair.split('='); - params[kp[0]] = kp[1]; - }); - - assert(_request.calledTwice); - assert.equal(req.bids[0].params.zone, parseInt(params['zone'])); - assert.equal(req.bids[0].params.zone, parseInt(params['id'])); - assert.equal('1', params['c']); - assert.equal(window.top.innerHeight, parseInt(params['wh'])); - assert.equal(window.top.innerWidth, parseInt(params['ww'])); - assert.equal(window.top.screen.width, parseInt(params['sw'])); - assert.equal(window.top.screen.height, parseInt(params['sh'])); - }); - - it('should parse bid allow param and send proper arguments to the server', () => { - const _request = adLoader.loadScript; - const req = { - bidderCode: 'justpremium', - bids: [ - { - bidId: 'bidId1', - bidder: 'justpremium', - params: { - zone: 20000, - allow: ['wp'] - }, - sizes: [[1, 1]], - placementCode: 'div-gpt-ad-1471513102552-1' - } - ] - }; - - adapter.callBids(req); - - const params = {}; - _request.getCall(1).args[0].split('?').pop().split('&').forEach(keypair => { - const kp = keypair.split('='); - params[kp[0]] = kp[1]; - }); - - assert(_request.calledTwice); - assert.equal(encodeURIComponent('[["wp"],[]]'), params['c']); - }); - - it('should parse bid exclude param and send proper arguments to the server', () => { - const _request = adLoader.loadScript; - const req = { - bidderCode: 'justpremium', - bids: [ - { - bidId: 'bidId1', - bidder: 'justpremium', - params: { - zone: 20000, - exclude: ['wp', 'lb'] - }, - sizes: [[1, 1]], - placementCode: 'div-gpt-ad-1471513102552-1' - } - ] - }; - - adapter.callBids(req); - - const params = {}; - _request.getCall(1).args[0].split('?').pop().split('&').forEach(keypair => { - const kp = keypair.split('='); - params[kp[0]] = kp[1]; - }); - - assert(_request.calledTwice); - assert.equal(encodeURIComponent('[[],["wp","lb"]]'), params['c']); - }); - - it('should add empty bid if there was no valid response', () => { - adLoader.loadScript.restore(); - const stubLoadScript = sandbox.stub(adLoader, 'loadScript', (url, callback) => { - if (callback) { - callback(new Error('test')); + let expectedResponse = [ + { + requestId: '319a5029c362f4', + creativeId: 3213123, + width: 970, + height: 250, + ad: 'creative code', + cpm: 0.52, + netRevenue: true, + currency: 'USD', + ttl: 60000 } - }); - const stubUtilLogError = sandbox.stub(utils, 'logError'); - const stubAddBidResponse = sandbox.stub(bidmanager, 'addBidResponse'); - const req = { - bidderCode: 'justpremium', - bids: [ - { - bidId: 'bidId1', - bidder: 'justpremium', - params: { - zone: 20000, - allow: ['wp'] - }, - sizes: [[1, 1]], - placementCode: 'div-gpt-ad-1471513102552-1' - } - ] - }; - - adapter.callBids(req); - - const bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; - const bidResponse = stubAddBidResponse.getCall(0).args[1]; - - assert(stubLoadScript.calledTwice); - assert(stubUtilLogError.calledOnce); - assert(stubAddBidResponse.calledOnce); - expect(bidPlacementCode).to.equal('div-gpt-ad-1471513102552-1'); - expect(bidResponse.getStatusCode()).to.equal(PREBID_CONSTANTS.STATUS.NO_BID); - expect(bidResponse.bidderCode).to.equal('justpremium'); - }); - - it('should add empty bid if response was empty', () => { - adLoader.loadScript.restore(); - const req = { - bidderCode: 'justpremium', - bids: [ - { - bidId: 'bidId1', - bidder: 'justpremium', - params: { - zone: 20000, - allow: ['wp'] - }, - sizes: [[1, 1]], - placementCode: 'div-gpt-ad-1471513102552-1' - } - ] - }; - - const stubLoadScript = sandbox.stub(adLoader, 'loadScript', (url, callback) => { - if (callback) { - callback(); - } - }); - const stubAddBidResponse = sandbox.stub(bidmanager, 'addBidResponse'); - const stubCreateBid = sandbox.stub(jPAM.cb.bidder20000, 'createBid', (factory) => { - return factory(); - }); - - adapter.callBids(req); - - const bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; - const bidResponse = stubAddBidResponse.getCall(0).args[1]; - - assert(stubAddBidResponse.calledOnce); - expect(stubLoadScript.callCount).to.be.equal(2); - expect(stubCreateBid.callCount).to.be.equal(1); - expect(bidPlacementCode).to.equal('div-gpt-ad-1471513102552-1'); - expect(bidResponse.getStatusCode()).to.equal(PREBID_CONSTANTS.STATUS.NO_BID); - expect(bidResponse.bidderCode).to.equal('justpremium'); - }); - - it('should add bid if tag contains any', () => { - adLoader.loadScript.restore(); - const req = { - bidderCode: 'justpremium', - bids: [ - { - bidId: 'bidId1', - bidder: 'justpremium', - params: { - zone: 20000, - allow: ['wp'] - }, - sizes: [[1, 1]], - placementCode: 'div-gpt-ad-1471513102552-2' - } - ] - }; - const responseData = { - width: 1, - height: 1, - ad: ``, - cpm: 5.98, - format: 'wp' - }; - - const stubLoadScript = sandbox.stub(adLoader, 'loadScript', (url, callback) => { - if (callback) { - callback(); + ] + + let result = spec.interpretResponse({body: response}, request) + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])) + + expect(result[0]).to.not.equal(null) + expect(result[0].width).to.equal(970) + expect(result[0].height).to.equal(250) + expect(result[0].ad).to.equal('creative code') + expect(result[0].cpm).to.equal(0.52) + expect(result[0].currency).to.equal('USD') + expect(result[0].ttl).to.equal(60000) + expect(result[0].creativeId).to.equal(3213123) + expect(result[0].netRevenue).to.equal(true) + }) + + it('Verify wrong server response', () => { + let response = { + 'bid': { + '28313': [] + }, + 'pass': { + '28313': true } - }); - const stubAddBidResponse = sandbox.stub(bidmanager, 'addBidResponse'); - const stubCreateBid = sandbox.stub(jPAM.cb.bidder20000, 'createBid', (factory) => { - const bid = factory({}); - - Object.assign(bid, responseData); - - return bid; - }); - - adapter.callBids(req); - - const bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - const bidResponse1 = stubAddBidResponse.getCall(0).args[1]; + } - assert(stubAddBidResponse.calledOnce); - expect(stubLoadScript.callCount).to.be.equal(2); - expect(stubCreateBid.callCount).to.be.equal(1); - expect(bidPlacementCode1).to.equal('div-gpt-ad-1471513102552-2'); - expect(bidResponse1.getStatusCode()).to.equal(PREBID_CONSTANTS.STATUS.GOOD); - expect(bidResponse1.bidderCode).to.equal('justpremium'); - expect(bidResponse1.width).to.equal(responseData.width); - expect(bidResponse1.height).to.equal(responseData.height); - expect(bidResponse1.cpm).to.equal(responseData.cpm); - expect(bidResponse1.format).to.equal(responseData.format); - expect(bidResponse1.ad).to.equal(responseData.ad); - }); - }); -}); + let result = spec.interpretResponse({body: response}, request) + expect(result.length).to.equal(0) + }) + }) + + describe('getUserSyncs', () => { + it('Verifies sync options', () => { + const options = spec.getUserSyncs({iframeEnabled: true}) + expect(options).to.not.be.undefined + expect(options[0].type).to.equal('iframe') + expect(options[0].src).to.match(/\/\/us-u.openx.net\/w\/1.0/) + }) + }) +}) diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index f74457d9f9b..0e896b4fc04 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -1,25 +1,49 @@ -describe('kargo adapter tests', function () { - const expect = require('chai').expect; - const assert = require('chai').assert; - const adapter = require('modules/kargoBidAdapter'); - const bidmanager = require('src/bidmanager'); - const bidfactory = require('src/bidfactory'); - const adloader = require('src/adloader'); - const CONSTANTS = require('src/constants.json'); +import {expect, assert} from 'chai'; +import {spec} from 'modules/kargoBidAdapter'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import {config} from 'src/config'; - var sandbox, params, krakenParams, adUnits, bidFactorySpy, addBidResponseSpy, bodyAppendSpy, cookies = [], localStorageItems = []; +describe('kargo adapter tests', function () { + var sandbox; beforeEach(() => { sandbox = sinon.sandbox.create(); - addBidResponseSpy = sandbox.stub(bidmanager, 'addBidResponse'); - bodyAppendSpy = sandbox.stub(document.body, 'appendChild'); - simulateBidFactory(); - simulateAdLoader(); - - params = { - timeout: 200, - requestId: 'f4cf851b-665a-43d7-b22c-33c8fdebe577', - bids: [ + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('bid request validity', function() { + it('passes when the bid includes a placement ID', function() { + assert(spec.isBidRequestValid({params: {placementId: 'foo'}}) === true); + }); + + it('fails when the bid does not include a placement ID', function() { + assert(spec.isBidRequestValid({params: {}}) === false); + }); + + it('fails when bid is falsey', function() { + assert(spec.isBidRequestValid() === false); + }); + + it('fails when the bid has no params at all', function() { + assert(spec.isBidRequestValid({}) === false); + }); + }); + + describe('build request', function() { + var bids, cookies = [], localStorageItems = []; + + beforeEach(() => { + sandbox.stub(config, 'getConfig').callsFake(function(key) { + if (key === 'currency') { + return 'USD'; + } + throw new Error(`Config stub incomplete! Missing key "${key}"`) + }); + + bids = [ { params: { placementId: 'foo' @@ -32,321 +56,301 @@ describe('kargo adapter tests', function () { }, placementCode: 2 } - ] - }; + ]; + }); - adUnits = { - foo: { - receivedTracker: 'fake-tracker-1', - cpm: 3, - adm: '
', - width: 320, - height: 50 - }, - bar: { - cpm: 2.5, - adm: '
', - width: 300, - height: 250 + afterEach(() => { + for (let key in cookies) { + let cookie = cookies[key]; + removeCookie(cookie); } + + for (let key in localStorageItems) { + let localStorageItem = localStorageItems[key]; + localStorage.removeItem(localStorageItem); + } + + cookies.length = 0; + localStorageItems.length = 0; + }); + + function setCookie(cname, cvalue, exdays = 1) { + _setCookie(cname, cvalue, exdays); + cookies.push(cname); } - }); - afterEach(() => { - sandbox.restore(); + function removeCookie(cname) { + _setCookie(cname, '', -1); + } - for (let key in cookies) { - let cookie = cookies[key]; - removeCookie(cookie); + function _setCookie(cname, cvalue, exdays = 1) { + var d = new Date(), + expires; + + d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); + expires = `expires=${d.toUTCString()}`; + document.cookie = `${cname}=${cvalue};${expires};path=/`; } - for (let key in localStorageItems) { - let localStorageItem = localStorageItems[key]; - localStorage.removeItem(localStorageItem); + function setLocalStorageItem(name, val) { + localStorage.setItem(name, val); + localStorageItems.push(name); } - cookies.length = 0; - localStorageItems.length = 0; - }); + function simulateNoLocalStorage() { + return sandbox.stub(localStorage, 'getItem').throws(); + } - function setCookie(cname, cvalue, exdays = 1) { - _setCookie(cname, cvalue, exdays); - cookies.push(cname); - } - - function removeCookie(cname) { - _setCookie(cname, '', -1); - } - - function _setCookie(cname, cvalue, exdays = 1) { - var d = new Date(), - expires; - - d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); - expires = `expires=${d.toUTCString()}`; - document.cookie = `${cname}=${cvalue};${expires};path=/`; - } - - function setLocalStorageItem(name, val) { - localStorage.setItem(name, val); - localStorageItems.push(name); - } - - function simulateAdLoader() { - sandbox.stub(adloader, 'loadScript', (url) => { - window.$$PREBID_GLOBAL$$.kargo_prebid_f4cf851b_665a_43d7_b22c_33c8fdebe577(adUnits); - krakenParams = JSON.parse(decodeURIComponent(url.match(/\?json=(.*)&cb=/)[1])); - }); - } + function initializeKruxUser() { + setLocalStorageItem('kxkar_user', 'rsgr9pnij'); + } - function simulateNoLocalStorage() { - return sandbox.stub(localStorage, 'getItem').throws(); - } + function initializeKruxSegments() { + setLocalStorageItem('kxkar_segs', 'qv9v984dy,rpx2gy365,qrd5u4axv,rnub9nmtd,reha00jnu'); + } - function simulateBidFactory() { - bidFactorySpy = sandbox.stub(bidfactory, 'createBid').withArgs(CONSTANTS.STATUS.GOOD); + 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'); + } - bidFactorySpy.onCall(0).returns({ - statusMessage: 'Bid available', - adId: '12dd646671a959' - }); + function getKrgCrb() { + return '%7B%22v%22%3A%22eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwiZXhwaXJlVGltZSI6MTQ5NzQ0OTM4MjY2OCwibGFzdFN5bmNlZEF0IjoxNDk3MzYyOTc5MDEyfQ%3D%3D%22%7D'; + } - bidFactorySpy.onCall(1).returns({ - statusMessage: 'Bid available', - adId: '33f07659bdaf94' - }); - } - - function initializeKruxUser() { - setLocalStorageItem('kxkar_user', 'rsgr9pnij'); - } - - function initializeKruxSegments() { - 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 initializeKrgCrb() { - setCookie('krg_crb', '%7B%22v%22%3A%22eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwiZXhwaXJlVGltZSI6MTQ5NzQ0OTM4MjY2OCwibGFzdFN5bmNlZEF0IjoxNDk3MzYyOTc5MDEyfQ%3D%3D%22%7D'); - } - - function initializeInvalidKrgUid() { - setCookie('krg_uid', 'invalid-krg-uid'); - } - - function initializeInvalidKrgCrbType1() { - setCookie('krg_crb', 'invalid-krg-crb'); - } - - function initializeInvalidKrgCrbType2() { - setCookie('krg_crb', '%7B%22v%22%3A%22%26%26%26%26%26%26%22%7D'); - } - - function initializeInvalidKrgCrbType3() { - setCookie('krg_crb', '%7B%22v%22%3A%22Ly8v%22%7D'); - } - - function initializeEmptyKrgUid() { - setCookie('krg_uid', '%7B%7D'); - } - - function initializeEmptyKrgCrb() { - setCookie('krg_crb', '%7B%22v%22%3A%22eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9%22%7D'); - } - - function getExpectedKrakenParams(excludeUserIds, excludeKrux) { - var base = { - timeout: 200, - currency: 'USD', - cpmGranularity: 1, - cpmRange: { - floor: 0, - ceil: 20 - }, - adSlotIds: [ - 'foo', - 'bar' - ], - userIDs: { - kargoID: '5f108831-302d-11e7-bf6b-4595acd3bf6c', - clientID: '2410d8f2-c111-4811-88a5-7b5e190e475f', - crbIDs: { - 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', - 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', - 23: 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', - 24: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', - 25: '5ee24138-5e03-4b9d-a953-38e833f2849f', - '2_80': 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', - '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' - }, - optOut: false - }, - krux: { - userID: 'rsgr9pnij', - segments: [ - 'qv9v984dy', - 'rpx2gy365', - 'qrd5u4axv', - 'rnub9nmtd', - 'reha00jnu' - ] - }, - pageURL: window.location.href - }; - - if (excludeUserIds === true) { - base.userIDs = { - crbIDs: {} - }; - } else if (excludeUserIds) { - if (excludeUserIds.uid) { - delete base.userIDs.kargoID; - delete base.userIDs.clientID; - delete base.userIDs.optOut; - } + function initializeKrgCrb() { + setCookie('krg_crb', getKrgCrb()); + } - if (excludeUserIds.crb) { - base.userIDs.crbIDs = {}; - } + function initializeInvalidKrgUid() { + setCookie('krg_uid', 'invalid-krg-uid'); } - if (excludeKrux) { - base.krux = { - userID: null, - segments: [] - }; + function getInvalidKrgCrbType1() { + return 'invalid-krg-crb'; } - return base; - } - - function getExpectedFirstBid() { - return { - 'bidderCode': 'kargo', - 'width': 320, - 'height': 50, - 'statusMessage': 'Bid available', - 'adId': '12dd646671a959', - 'cpm': 3, - 'ad': '
' - }; - } - - function getExpectedSecondBid() { - return { - 'bidderCode': 'kargo', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '33f07659bdaf94', - 'cpm': 2.5, - 'ad': '
' - }; - } - - function generalAssertions() { - assert(bidFactorySpy.calledTwice); - - assert(addBidResponseSpy.getCall(0).calledWithExactly(1, sinon.match(getExpectedFirstBid()))); - assert(addBidResponseSpy.getCall(1).calledWithExactly(2, sinon.match(getExpectedSecondBid()))); - assert(addBidResponseSpy.calledTwice); - - var trackerEl = bodyAppendSpy.getCall(0).args[0]; - assert(trackerEl instanceof HTMLImageElement); - assert(trackerEl.src === `${window.location.origin}/fake-tracker-1`); - assert(bodyAppendSpy.calledOnce); - } - - it('works when all params and cookies are correctly set', function() { - initializeKruxUser(); - initializeKruxSegments(); - initializeKrgUid(); - initializeKrgCrb(); - - adapter().callBids(params); - - generalAssertions(); - expect(krakenParams).to.deep.equal(getExpectedKrakenParams()); - }); + function initializeInvalidKrgCrbType1() { + setCookie('krg_crb', getInvalidKrgCrbType1()); + } - it('gracefully handles nothing being set', function() { - adapter().callBids(params); + function getInvalidKrgCrbType2() { + return '%7B%22v%22%3A%22%26%26%26%26%26%26%22%7D'; + } - generalAssertions(); - expect(krakenParams).to.deep.equal(getExpectedKrakenParams(true, true)); - }); + function initializeInvalidKrgCrbType2() { + setCookie('krg_crb', getInvalidKrgCrbType2()); + } - it('gracefully handles browsers without localStorage', function() { - simulateNoLocalStorage(); - initializeKrgUid(); - initializeKrgCrb(); + function getInvalidKrgCrbType3() { + return '%7B%22v%22%3A%22Ly8v%22%7D'; + } - adapter().callBids(params); + function initializeInvalidKrgCrbType3() { + setCookie('krg_crb', getInvalidKrgCrbType3()); + } - generalAssertions(); - expect(krakenParams).to.deep.equal(getExpectedKrakenParams(false, true)); - }); + function initializeEmptyKrgUid() { + setCookie('krg_uid', '%7B%7D'); + } - it('handles empty yet valid Kargo CRBs and UIDs', function() { - initializeKruxUser(); - initializeKruxSegments(); - initializeEmptyKrgUid(); - initializeEmptyKrgCrb(); + function getEmptyKrgCrb() { + return '%7B%22v%22%3A%22eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9%22%7D'; + } - adapter().callBids(params); + function initializeEmptyKrgCrb() { + setCookie('krg_crb', getEmptyKrgCrb()); + } - generalAssertions(); - expect(krakenParams).to.deep.equal(getExpectedKrakenParams(true)); - }); + function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB) { + var base = { + timeout: 200, + currency: 'USD', + cpmGranularity: 1, + cpmRange: { + floor: 0, + ceil: 20 + }, + adSlotIds: [ + 'foo', + 'bar' + ], + userIDs: { + kargoID: '5f108831-302d-11e7-bf6b-4595acd3bf6c', + clientID: '2410d8f2-c111-4811-88a5-7b5e190e475f', + crbIDs: { + 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', + 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', + 23: 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', + 24: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', + 25: '5ee24138-5e03-4b9d-a953-38e833f2849f', + '2_80': 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', + '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' + }, + optOut: false + }, + krux: { + userID: 'rsgr9pnij', + segments: [ + 'qv9v984dy', + 'rpx2gy365', + 'qrd5u4axv', + 'rnub9nmtd', + 'reha00jnu' + ] + }, + pageURL: window.location.href, + rawCRB: expectedRawCRB + }; - it('handles broken Kargo UIDs', function() { - initializeKruxUser(); - initializeKruxSegments(); - initializeInvalidKrgUid(); - initializeKrgCrb(); + if (excludeUserIds === true) { + base.userIDs = { + crbIDs: {} + }; + } else if (excludeUserIds) { + if (excludeUserIds.uid) { + delete base.userIDs.kargoID; + delete base.userIDs.clientID; + delete base.userIDs.optOut; + } - adapter().callBids(params); + if (excludeUserIds.crb) { + base.userIDs.crbIDs = {}; + } + } - generalAssertions(); - expect(krakenParams).to.deep.equal(getExpectedKrakenParams({uid: true})); - }); + if (excludeKrux) { + base.krux = { + userID: null, + segments: [] + }; + } - it('handles broken Kargo CRBs where top level JSON is invalid', function() { - initializeKruxUser(); - initializeKruxSegments(); - initializeKrgUid(); - initializeInvalidKrgCrbType1(); + return base; + } - adapter().callBids(params); + function testBuildRequests(expected) { + var request = spec.buildRequests(bids, {timeout: 200, foo: 'bar'}); + 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/v1/bid'); + expect(request.method).to.equal('GET'); + expect(request.currency).to.equal('USD'); + expect(request.timeout).to.equal(200); + expect(request.foo).to.equal('bar'); + expect(krakenParams).to.deep.equal(expected); + } - generalAssertions(); - expect(krakenParams).to.deep.equal(getExpectedKrakenParams({crb: true})); - }); + it('works when all params and cookies are correctly set', function() { + initializeKruxUser(); + initializeKruxSegments(); + initializeKrgUid(); + initializeKrgCrb(); + testBuildRequests(getExpectedKrakenParams(undefined, undefined, getKrgCrb())); + }); - it('handles broken Kargo CRBs where inner base 64 is invalid', function() { - initializeKruxUser(); - initializeKruxSegments(); - initializeKrgUid(); - initializeInvalidKrgCrbType2(); + it('gracefully handles nothing being set', function() { + testBuildRequests(getExpectedKrakenParams(true, true, null)); + }); - adapter().callBids(params); + it('gracefully handles browsers without localStorage', function() { + simulateNoLocalStorage(); + initializeKrgUid(); + initializeKrgCrb(); + testBuildRequests(getExpectedKrakenParams(false, true, getKrgCrb())); + }); - generalAssertions(); - expect(krakenParams).to.deep.equal(getExpectedKrakenParams({crb: true})); - }); + it('handles empty yet valid Kargo CRBs and UIDs', function() { + initializeKruxUser(); + initializeKruxSegments(); + initializeEmptyKrgUid(); + initializeEmptyKrgCrb(); + testBuildRequests(getExpectedKrakenParams(true, undefined, getEmptyKrgCrb())); + }); - it('handles broken Kargo CRBs where inner JSON is invalid', function() { - initializeKruxUser(); - initializeKruxSegments(); - initializeKrgUid(); - initializeInvalidKrgCrbType3(); + it('handles broken Kargo UIDs', function() { + initializeKruxUser(); + initializeKruxSegments(); + initializeInvalidKrgUid(); + initializeKrgCrb(); + testBuildRequests(getExpectedKrakenParams({uid: true}, undefined, getKrgCrb())); + }); + + it('handles broken Kargo CRBs where top level JSON is invalid', function() { + initializeKruxUser(); + initializeKruxSegments(); + initializeKrgUid(); + initializeInvalidKrgCrbType1(); + testBuildRequests(getExpectedKrakenParams({crb: true}, undefined, getInvalidKrgCrbType1())); + }); - adapter().callBids(params); + it('handles broken Kargo CRBs where inner base 64 is invalid', function() { + initializeKruxUser(); + initializeKruxSegments(); + initializeKrgUid(); + initializeInvalidKrgCrbType2(); + testBuildRequests(getExpectedKrakenParams({crb: true}, undefined, getInvalidKrgCrbType2())); + }); + + it('handles broken Kargo CRBs where inner JSON is invalid', function() { + initializeKruxUser(); + initializeKruxSegments(); + initializeKrgUid(); + initializeInvalidKrgCrbType3(); + testBuildRequests(getExpectedKrakenParams({crb: true}, undefined, getInvalidKrgCrbType3())); + }); + }); - generalAssertions(); - expect(krakenParams).to.deep.equal(getExpectedKrakenParams({crb: true})); + describe('response handler', function() { + it('handles bid responses', function() { + var resp = spec.interpretResponse({body: { + foo: { + cpm: 3, + adm: '
', + width: 320, + height: 50 + }, + bar: { + cpm: 2.5, + adm: '
', + width: 300, + height: 250 + } + }}, { + currency: 'USD', + bids: [{ + bidId: 'fake bid id 1', + params: { + placementId: 'foo' + } + }, { + bidId: 'fake bid id 2', + params: { + placementId: 'bar' + } + }] + }); + var expectation = [{ + requestId: 'fake bid id 1', + cpm: 3, + width: 320, + height: 50, + ad: '
', + ttl: 300, + creativeId: 'foo', + netRevenue: true, + currency: 'USD' + }, { + requestId: 'fake bid id 2', + cpm: 2.5, + width: 300, + height: 250, + ad: '
', + ttl: 300, + creativeId: 'bar', + netRevenue: true, + currency: 'USD' + }]; + expect(resp).to.deep.equal(expectation); + }); }); }); diff --git a/test/spec/modules/komoonaBidAdapter_spec.js b/test/spec/modules/komoonaBidAdapter_spec.js index 2657c658ba2..f7038505db3 100644 --- a/test/spec/modules/komoonaBidAdapter_spec.js +++ b/test/spec/modules/komoonaBidAdapter_spec.js @@ -1,152 +1,164 @@ import { expect } from 'chai'; -import Adapter from 'modules/komoonaBidAdapter'; -import bidmanager from 'src/bidmanager'; +import { spec } from 'modules/komoonaBidAdapter'; -const ENDPOINT = '//bidder.komoona.com/v1/GetSBids'; - -const REQUEST = { - 'bidderCode': 'komoona', - 'requestId': '1f43cc36a6a7e', - 'bidderRequestId': '25392d757fad47', - 'bids': [ +describe('Komoona.com Adapter Tests', () => { + const bidsRequest = [ { - 'bidder': 'komoona', - 'params': { - 'hbid': 'abcd666dcba', - 'placementId': 'abcd123123dcba' + bidder: 'komoona', + params: { + placementId: '170577', + hbid: 'abc12345678', }, - 'placementCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [ + placementCode: 'div-gpt-ad-1460505748561-0', + transactionId: '9f801c02-bbe8-4683-8ed4-bc816ea186bb', + sizes: [ [300, 250] ], - 'bidId': '30e5e911c00703', - 'bidderRequestId': '25392d757fad47', - 'requestId': '1f43cc36a6a7e' - } - ], - 'start': 1466493146527 -}; - -const RESPONSE = { - 'bids': [ + bidId: '2faedf1095f815', + bidderRequestId: '18065867f8ae39', + auctionId: '529e1518-b872-45cf-807c-2d41dfa5bcd3' + }, { - 'placementid': 'abcd123123dcba', - 'uuid': '30e5e911c00703', - 'width': 728, - 'height': 90, - 'cpm': 0.5, - 'creative': '' + bidder: 'komoona', + params: { + placementId: '281277', + hbid: 'abc12345678', + 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' + } + ] } - ] -}; - -describe('komoonaAdapter', () => { - let adapter; - - beforeEach(() => adapter = new Adapter()); - - describe('request function', () => { - let xhr; - let requests; - let pbConfig; - - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - pbConfig = REQUEST; - // just a single slot - pbConfig.bids = [pbConfig.bids[0]]; - }); - - afterEach(() => xhr.restore()); - - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - - it('requires paramters to make request', () => { - adapter.callBids({}); - expect(requests).to.be.empty; - }); - - it('requires placementid and hbid', () => { - let backup = pbConfig.bids[0].params; - pbConfig.bids[0].params = {placementid: 1234}; // no hbid - adapter.callBids(pbConfig); - expect(requests).to.be.empty; + }; - pbConfig.bids[0].params = {hbid: 1234}; // no placementid - adapter.callBids(pbConfig); - expect(requests).to.be.empty; - - pbConfig.bids[0].params = backup; - }); - - it('sends bid request to ENDPOINT via POST', () => { - adapter.callBids(pbConfig); - expect(requests[0].url).to.equal(ENDPOINT); - expect(requests[0].method).to.equal('POST'); - }); + it('Verifies komoonaAdapter bidder code', () => { + expect(spec.code).to.equal('komoona'); }); - describe('response handler', () => { - let server; - - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); - }); - - afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); - }); - - it('registers bids', () => { - server.respondWith(JSON.stringify(RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('cpm', 0.5); - }); - - it('handles nobid responses', () => { - server.respondWith(JSON.stringify({ - 'bids': [{ - 'cpm': 0, - 'creative': '', - 'uuid': '30e5e911c00703' - }] - })); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); - }); + it('Verifies komoonaAdapter bid request validation', () => { + 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('handles JSON.parse errors', () => { - server.respondWith(''); + it('Verify komoonaAdapter build request', () => { + var startTime = new Date().getTime(); + + const request = spec.buildRequests(bidsRequest); + expect(request.url).to.equal('//bidder.komoona.com/v1/GetSBids'); + 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('abc12345678'); + 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('abc12345678'); + 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); + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('Verify komoonaAdapter build response', () => { + 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); + }); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); - }); + it('Verifies komoonaAdapter sync options', () => { + // 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('//s.komoona.com/sync/usync.html'); }); }); diff --git a/test/spec/modules/kummaBidAdapter_spec.js b/test/spec/modules/kummaBidAdapter_spec.js index 82a2823efd7..d90063820b7 100644 --- a/test/spec/modules/kummaBidAdapter_spec.js +++ b/test/spec/modules/kummaBidAdapter_spec.js @@ -1,156 +1,97 @@ -describe('kumma adapter tests', function () { - var expect = require('chai').expect; - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - var adapter = require('modules/kummaBidAdapter'); - var adLoader = require('src/adloader'); - var bidmanager = require('src/bidmanager'); - - var stubLoadScript; - - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - - afterEach(function () { - stubLoadScript.restore(); - }); - - describe('creation of bid url', function () { - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; +import {expect} from 'chai'; +import {spec} from 'modules/kummaBidAdapter'; +import {getTopWindowLocation, getTopWindowReferrer} from 'src/utils'; + +describe('Kumma Adapter Tests', () => { + const slotConfigs = [{ + placementCode: '/DfpAccount1/slot1', + sizes: [[300, 250]], + bidId: 'bid12345', + params: { + pubId: '55879', + siteId: '26047', + placementId: '123', + bidFloor: '0.001' } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; + }, { + placementCode: '/DfpAccount2/slot2', + sizes: [[250, 250]], + bidId: 'bid23456', + params: { + pubId: '55879', + siteId: '26047', + placementId: '456' } - - it('bid request for single placement', function () { - var params = { - bids: [{ - placementCode: '/19968336/header-bid-tag-0', - sizes: [[300, 250]], - bidId: 'bid1111', - bidder: 'kumma', - params: { pubId: '37054', siteId: '123' } - }] - }; - - adapter().callBids(params); - - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledOnce(stubLoadScript); - - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - expect(parsedBidUrlQueryString).to.have.property('pub_id').and.to.equal('37054'); - expect(parsedBidUrlQueryString).to.have.property('site_id').and.to.equal('123'); - expect(parsedBidUrlQueryString).to.have.property('width').and.to.equal('300'); - expect(parsedBidUrlQueryString).to.have.property('height').and.to.equal('250'); - }); + }]; + it('Verify build request', () => { + const request = spec.buildRequests(slotConfigs); + expect(request.url).to.equal('//hb.kumma.com/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(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('55879'); + expect(ortbRequest.site.ref).to.equal(getTopWindowReferrer()); + expect(ortbRequest.site.page).to.equal(getTopWindowLocation().href); + expect(ortbRequest.imp).to.have.lengthOf(2); + // device object + expect(ortbRequest.device).to.not.equal(null); + expect(ortbRequest.device.ua).to.equal(navigator.userAgent); + // slot 1 + expect(ortbRequest.imp[0].tagid).to.equal('123'); + expect(ortbRequest.imp[0].banner).to.not.equal(null); + expect(ortbRequest.imp[0].banner.w).to.equal(300); + expect(ortbRequest.imp[0].banner.h).to.equal(250); + expect(ortbRequest.imp[0].bidfloor).to.equal('0.001'); + // slot 2 + expect(ortbRequest.imp[1].tagid).to.equal('456'); + expect(ortbRequest.imp[1].banner).to.not.equal(null); + expect(ortbRequest.imp[1].banner.w).to.equal(250); + expect(ortbRequest.imp[1].banner.h).to.equal(250); + expect(ortbRequest.imp[1].bidfloor).to.equal('0.000001'); }); - describe('handling bid response', function () { - it('should return complete bid response', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bids: [{ - placementCode: '/19968336/header-bid-tag-0', - sizes: [[300, 250]], - bidId: 'bid1111', - bidder: 'kumma', - params: { pubId: '37054', siteId: '123' } - }] - }; - - var response = { - cpm: 1, - width: 300, - height: 250, - callback_uid: 'bid1111', - tag: '' : getVASTAd(); + return { + status: noBid ? 0 : 1, + cpm: 1.0, + width: 160, + height: 600, + creativeId: 'test', + dealId: 'test', + content: noBid ? noBidContent : content, + content_type: isBanner ? 'display' : 'vast_2_0' + }; +}; - it('jstag_url is not provided', () => { - request.bids[0].params.jstag_url = ''; - adapter.callBids(request); - expect(tagRequests).to.be.empty; - }); +describe('LifestreetAdapter', () => { + const LIFESTREET_URL = '//ads.lfstmedia.com/gate/'; + const ADAPTER_VERSION = 'prebidJS-2.0'; - it('should request a tag', () => { - window.LSM_Slot = undefined; - adapter.callBids(request); - expect(tagRequests.length).to.equal(1); - expect(tagRequests[0]).to.contain('ads.lfstmedia.com/getad?site=285071'); - }); + describe('buildRequests()', () => { + it('method exists and is a function', () => { + expect(spec.buildRequests).to.exist.and.to.be.a('function'); + }); - it('LSM_Slot function should contain expected parameters', () => { - adapter.callBids(request); - expect(slotParams.ad_size).to.equal('160x600'); - expect(slotParams.adkey).to.equal('78c'); - expect(slotParams.slot).to.equal('slot166704'); - expect(slotParams._preload).to.equal('wait'); - expect(slotParams._hb_request).to.equal('prebidJS-1.0'); - expect(slotParams._timeout).to.equal(1500); - expect(slotParams).to.have.ownProperty('_onload'); - }); + it('should not return request when no bids are present', () => { + let [request] = spec.buildRequests([]); + expect(request).to.be.empty; + }); - it('Default timeout should be 700 milliseconds', () => { - request.bids[0].params.timeout = 0; - adapter.callBids(request); - expect(slotParams._timeout).to.equal(700); - }); + let bidRequest = getDefaultBidRequest(); + let [request] = spec.buildRequests(bidRequest.bids); + it('should return request for Lifestreet endpoint', () => { + expect(request.url).to.contain(LIFESTREET_URL); }); - describe('response', () => { - let slot; - let price; - let width; - let height; + it('should use json adapter', () => { + expect(request.url).to.contain('/prebid/'); + }); - beforeEach(() => { - sinon.stub(bidmanager, 'addBidResponse'); - sinon.stub(adloader, 'loadScript', (url, callback) => { - callback(); - }); - slot = {}; - price = 1.0; - width = 160; - height = 600; - window.LSM_Slot = (params) => { - params._onload(slot, '', price, width, height); - }; - }); - afterEach(() => { - bidmanager.addBidResponse.restore(); - adloader.loadScript.restore(); - window.LSM_Slot = undefined; - }); + it('should contain slot', () => { + expect(request.url).to.contain('slot166704'); + }); + it('should contain adkey', () => { + expect(request.url).to.contain('adkey=78c'); + }); + it('should contain ad_size', () => { + expect(request.url).to.contain('ad_size=160x600'); + }); - it('nobid for undefined LSM_Slot function', () => { - window.LSM_Slot = undefined; - adapter.callBids(BIDDER_REQUEST); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(2); - }); + it('should contain location and rferrer paramters', () => { + expect(request.url).to.contain('__location='); + expect(request.url).to.contain('__referrer='); + }); + it('should contain info parameters', () => { + expect(request.url).to.contain('__wn='); + expect(request.url).to.contain('__sf='); + expect(request.url).to.contain('__fif='); + expect(request.url).to.contain('__if='); + expect(request.url).to.contain('__stamp='); + expect(request.url).to.contain('__pp='); + }); - it('nobid for error response', () => { - slot.state = () => { return 'error'; }; - adapter.callBids(BIDDER_REQUEST); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(2); - }); + it('should contain HB enabled', () => { + expect(request.url).to.contain('__hb=1'); + }); + it('should include gzip', () => { + expect(request.url).to.contain('__gz=1'); + }); - it('show existing slot', () => { - let isShown = false; - slot.state = () => { return 'loaded'; }; - slot.getSlotObjectName = () => { return ''; }; - slot.show = () => { isShown = true; }; - adapter.callBids(BIDDER_REQUEST); - expect(bidmanager.addBidResponse.calledOnce).to.be.false; - expect(isShown).to.be.true; - }); + it('should contain the right version of adapter', () => { + expect(request.url).to.contain('__hbver=' + ADAPTER_VERSION); + }); + }); + describe('interpretResponse()', () => { + it('should return formatted bid response with required properties', () => { + let bidRequest = getDefaultBidRequest().bids[0]; + let bidResponse = { body: getDefaultBidResponse(true) }; + let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest); + expect(formattedBidResponse).to.deep.equal([{ + requestId: bidRequest.bidId, + cpm: 1.0, + width: 160, + height: 600, + ad: '', + creativeId: 'test', + currency: 'USD', + dealId: 'test', + netRevenue: true, + ttl: 86400, + mediaType: 'banner' + }]); + }); + it('should return formatted VAST bid response with required properties', () => { + let bidRequest = getDefaultBidRequest().bids[0]; + let bidResponse = { body: getDefaultBidResponse(false) }; + let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest); + expect(formattedBidResponse).to.deep.equal([{ + requestId: bidRequest.bidId, + cpm: 1.0, + width: 160, + height: 600, + vastXml: getVASTAd(), + creativeId: 'test', + currency: 'USD', + dealId: 'test', + netRevenue: true, + ttl: 86400, + mediaType: 'video' + }]); + }); + it('should return formatted VAST bid response with vastUrl', () => { + let bidRequest = getDefaultBidRequest().bids[0]; + let bidResponse = { body: getDefaultBidResponse(false) }; + bidResponse.body.vastUrl = 'http://lifestreet.com'; // set vastUrl + let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest); + expect(formattedBidResponse).to.deep.equal([{ + requestId: bidRequest.bidId, + cpm: 1.0, + width: 160, + height: 600, + vastUrl: 'http://lifestreet.com', + creativeId: 'test', + currency: 'USD', + dealId: 'test', + netRevenue: true, + ttl: 86400, + mediaType: 'video' + }]); + }); - it('should bid', () => { - slot.state = () => { return 'loaded'; }; - slot.getSlotObjectName = () => { return 'Test Slot'; }; - adapter.callBids(BIDDER_REQUEST); - expect(bidmanager.addBidResponse.calledOnce).to.be.true; - let bidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(bidResponse.getStatusCode()).to.equal(1); - expect(bidResponse.ad).to.equal(`
- - `); - expect(bidResponse.cpm).to.equal(1.0); - expect(bidResponse.width).to.equal(160); - expect(bidResponse.height).to.equal(600); - }); + it('should return no-bid', () => { + let bidRequest = getDefaultBidRequest().bids[0]; + let bidResponse = { body: getDefaultBidResponse(true, true) }; + let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest); + expect(formattedBidResponse).to.be.empty; }); }); }); diff --git a/test/spec/modules/lkqdBidAdapter_spec.js b/test/spec/modules/lkqdBidAdapter_spec.js new file mode 100644 index 00000000000..d3e16f0c99a --- /dev/null +++ b/test/spec/modules/lkqdBidAdapter_spec.js @@ -0,0 +1,343 @@ +import { spec } from 'modules/lkqdBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +const { expect } = require('chai'); + +describe('LKQD Bid Adapter Test', () => { + 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', () => { + let bid = { + 'bidder': 'lkqd', + 'params': { + 'siteId': '662921', + 'placementId': '263' + }, + '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', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + wrong: 'missing zone id' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const ENDPOINT = 'https://ssp.lkqd.net/ad?'; + let bidRequests = [ + { + 'bidder': 'lkqd', + 'params': { + 'siteId': '662921', + 'placementId': '263' + }, + 'adUnitCode': 'lkqd', + 'sizes': [[300, 250], [640, 480]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + } + ]; + let bidRequest = [ + { + 'bidder': 'lkqd', + 'params': { + 'siteId': '662921', + 'placementId': '263' + }, + 'adUnitCode': 'lkqd', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + } + ]; + + it('should populate available parameters', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests.length).to.equal(2); + const r1 = requests[0].url; + expect(r1).to.contain('?pid=263'); + expect(r1).to.contain('&sid=662921'); + expect(r1).to.contain('&width=300'); + expect(r1).to.contain('&height=250'); + const r2 = requests[1].url; + expect(r2).to.contain('?pid=263'); + expect(r2).to.contain('&sid=662921'); + expect(r2).to.contain('&width=640'); + expect(r2).to.contain('&height=480'); + }); + + it('should not populate unspecified parameters', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests.length).to.equal(2); + const r1 = requests[0].url; + expect(r1).to.contain('‌&contentid=[CONTENT_ID]'); + expect(r1).to.contain('‌&contenttitle=[CONTENT_TITLE]'); + expect(r1).to.contain('‌&contentlength=[CONTENT_LENGTH]'); + expect(r1).to.contain('&height=250'); + const r2 = requests[1].url; + expect(r2).to.contain('‌&contentid=[CONTENT_ID]'); + expect(r2).to.contain('‌&contenttitle=[CONTENT_TITLE]'); + expect(r2).to.contain('‌&contentlength=[CONTENT_LENGTH]'); + expect(r2).to.contain('‌&contenturl=[CONTENT_URL]'); + }); + + it('should handle single size request', () => { + const requests = spec.buildRequests(bidRequest); + expect(requests.length).to.equal(1); + const r1 = requests[0].url; + expect(r1).to.contain('?pid=263'); + expect(r1).to.contain('&sid=662921'); + expect(r1).to.contain('&width=640'); + expect(r1).to.contain('&height=480'); + }); + + it('sends bid request to ENDPOINT via GET', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests.length).to.equal(2); + const r1 = requests[0]; + expect(r1.url).to.contain(ENDPOINT); + expect(r1.method).to.equal('GET'); + const r2 = requests[1] + expect(r2.url).to.contain(ENDPOINT); + expect(r2.method).to.equal('GET'); + }); + }); + + describe('interpretResponse', () => { + let bidRequest = { + 'url': 'https://ssp.lkqd.net/ad?pid=263&sid=662921&output=vast&execution=any&placement=&playinit=auto&volume=100&timeout=&width=300%E2%80%8C&height=250&pbt=[PREBID_TOKEN]%E2%80%8C&dnt=[DO_NOT_TRACK]%E2%80%8C&pageurl=[PAGEURL]%E2%80%8C&contentid=[CONTENT_ID]%E2%80%8C&contenttitle=[CONTENT_TITLE]%E2%80%8C&contentlength=[CONTENT_LENGTH]%E2%80%8C&contenturl=[CONTENT_URL]&prebid=true%E2%80%8C&rnd=874313435?bidId=253dcb69fb2577&bidWidth=300&bidHeight=250&', + 'data': { + 'bidId': '253dcb69fb2577', + 'bidWidth': '640', + 'bidHeight': '480' + } + }; + let serverResponse = {}; + serverResponse.body = ` + + +LKQD + + + +2.87 + + + + + + + + + + + + + + + + + + + + + + + + +00:00:30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + +`; + + it('should correctly parse valid bid response', () => { + const BIDDER_CODE = 'lkqd'; + 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.ad).to.equal(''); + expect(bidResponse.cpm).to.equal(2.87); + expect(bidResponse.width).to.equal('640'); + expect(bidResponse.height).to.equal('480'); + expect(bidResponse.ttl).to.equal(300); + expect(bidResponse.creativeId).to.equal('677477'); + 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', () => { + let invalidServerResponse = {}; + invalidServerResponse.body = ''; + + let result = spec.interpretResponse(invalidServerResponse, bidRequest); + expect(result.length).to.equal(0); + }); + + it('handles nobid responses', () => { + let nobidResponse = {}; + nobidResponse.body = ''; + + let result = spec.interpretResponse(nobidResponse, bidRequest); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/lockerdomeBidAdapter_spec.js b/test/spec/modules/lockerdomeBidAdapter_spec.js new file mode 100644 index 00000000000..1ad26af24c1 --- /dev/null +++ b/test/spec/modules/lockerdomeBidAdapter_spec.js @@ -0,0 +1,142 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/lockerdomeBidAdapter'; +import * as utils from 'src/utils'; + +describe('LockerDomeAdapter', () => { + const bidRequests = [{ + bidder: 'lockerdome', + params: { + adUnitId: 10809467961050726 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + 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 + }, + mediaTypes: { + banner: { + sizes: [[300, 600]] + } + }, + adUnitCode: 'ad-2', + transactionId: '73459f05-c482-4706-b2b7-72e6f6264ce6', + sizes: [[300, 600]], + bidId: '4510f2834773ce', + bidderRequestId: '14a54fade69854', + auctionId: 'd4c83108-615d-4c2c-9384-dac9ffd4fd72' + }]; + + describe('isBidRequestValid', () => { + it('should return true if the adUnitId parameter is present', () => { + expect(spec.isBidRequestValid(bidRequests[0])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[1])).to.be.true; + }); + it('should return false if the adUnitId parameter is not present', () => { + let bidRequest = utils.deepClone(bidRequests[0]); + delete bidRequest.params.adUnitId; + expect(spec.isBidRequestValid(bidRequest)).to.be.false; + }); + }); + + describe('buildRequests', () => { + it('should generate a valid single POST request for multiple bid requests', () => { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + // TODO: Update to production URL + 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(bids[0].requestId).to.equal('2652ca954bce9'); + expect(bids[0].adUnitId).to.equal(10809467961050726); + 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].adUnitId).to.equal(9434769725128806); + 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); + }); + }); + + describe('interpretResponse', () => { + it('should return an empty array if an invalid response is passed', () => { + const interpretedResponse = spec.interpretResponse({ body: {} }); + expect(interpretedResponse).to.be.an('array').that.is.empty; + }); + + it('should return valid response when passed valid server response', () => { + const serverResponse = { + body: { + bids: [{ + requestId: '2652ca954bce9', + cpm: 1.00, + width: 300, + height: 250, + creativeId: '12345', + currency: 'USD', + netRevenue: true, + ad: '', + ttl: 300 + }, + { + requestId: '4510f2834773ce', + cpm: 1.10, + width: 300, + height: 600, + creativeId: '45678', + currency: 'USD', + netRevenue: true, + ad: '', + ttl: 300 + }] + } + }; + + const request = spec.buildRequests(bidRequests); + const interpretedResponse = spec.interpretResponse(serverResponse, request); + + expect(interpretedResponse).to.have.lengthOf(2); + + expect(interpretedResponse[0].requestId).to.equal(serverResponse.body.bids[0].requestId); + expect(interpretedResponse[0].cpm).to.equal(serverResponse.body.bids[0].cpm); + expect(interpretedResponse[0].width).to.equal(serverResponse.body.bids[0].width); + expect(interpretedResponse[0].height).to.equal(serverResponse.body.bids[0].height); + expect(interpretedResponse[0].creativeId).to.equal(serverResponse.body.bids[0].creativeId); + expect(interpretedResponse[0].currency).to.equal(serverResponse.body.bids[0].currency); + expect(interpretedResponse[0].netRevenue).to.equal(serverResponse.body.bids[0].netRevenue); + expect(interpretedResponse[0].ad).to.equal(serverResponse.body.bids[0].ad); + expect(interpretedResponse[0].ttl).to.equal(serverResponse.body.bids[0].ttl); + + expect(interpretedResponse[1].requestId).to.equal(serverResponse.body.bids[1].requestId); + expect(interpretedResponse[1].cpm).to.equal(serverResponse.body.bids[1].cpm); + expect(interpretedResponse[1].width).to.equal(serverResponse.body.bids[1].width); + expect(interpretedResponse[1].height).to.equal(serverResponse.body.bids[1].height); + expect(interpretedResponse[1].creativeId).to.equal(serverResponse.body.bids[1].creativeId); + expect(interpretedResponse[1].currency).to.equal(serverResponse.body.bids[1].currency); + expect(interpretedResponse[1].netRevenue).to.equal(serverResponse.body.bids[1].netRevenue); + expect(interpretedResponse[1].ad).to.equal(serverResponse.body.bids[1].ad); + expect(interpretedResponse[1].ttl).to.equal(serverResponse.body.bids[1].ttl); + }); + }); +}); diff --git a/test/spec/modules/madvertiseBidAdapter_spec.js b/test/spec/modules/madvertiseBidAdapter_spec.js new file mode 100644 index 00000000000..64ecf556aa5 --- /dev/null +++ b/test/spec/modules/madvertiseBidAdapter_spec.js @@ -0,0 +1,157 @@ +import {expect} from 'chai'; +import {spec} from 'modules/madvertiseBidAdapter'; + +describe('madvertise adapater', () => { + describe('Test validate req', () => { + it('should accept minimum valid bid', () => { + let bid = { + bidder: 'madvertise', + sizes: [[728, 90]], + params: { + s: 'test' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(true); + }); + it('should reject no sizes', () => { + let bid = { + bidder: 'madvertise', + params: { + s: 'test' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + it('should reject empty sizes', () => { + let bid = { + bidder: 'madvertise', + sizes: [], + params: { + s: 'test' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + it('should reject wrong format sizes', () => { + let bid = { + bidder: 'madvertise', + sizes: [['728x90']], + params: { + s: 'test' + } + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + it('should reject no params', () => { + let bid = { + bidder: 'madvertise', + sizes: [[728, 90]] + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + it('should reject missing s', () => { + let bid = { + bidder: 'madvertise', + params: {} + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + }); + + describe('Test build request', () => { + it('minimum request', () => { + let bid = [{ + bidder: 'madvertise', + sizes: [[728, 90], [300, 100]], + bidId: '51ef8751f9aead', + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + auctionId: '18fd8b8b0bd757', + bidderRequestId: '418b37f85e772c', + params: { + s: 'test', + } + }]; + const req = spec.buildRequests(bid); + + expect(req).to.exist.and.to.be.a('array'); + expect(req[0]).to.have.property('method'); + expect(req[0].method).to.equal('GET'); + expect(req[0]).to.have.property('url'); + expect(req[0].url).to.contain('//mobile.mng-ads.com/?rt=bid_request&v=1.0').and.to.contain(`&s=test`).and.to.contain(`&sizes[0]=728x90`) + }); + }); + + describe('Test interpret response', () => { + it('General banner response', () => { + let bid = { + bidder: 'madvertise', + sizes: [[728, 90]], + bidId: '51ef8751f9aead', + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + auctionId: '18fd8b8b0bd757', + bidderRequestId: '418b37f85e772c', + params: { + s: 'test', + connection_type: 'WIFI', + age: 25, + } + }; + let resp = spec.interpretResponse({body: { + requestId: 'REQUEST_ID', + cpm: 1, + ad: '

I am an ad

', + Width: 320, + height: 50, + creativeId: 'CREATIVE_ID', + dealId: 'DEAL_ID', + ttl: 180, + currency: 'EUR', + netRevenue: true + }}, {bidId: bid.bidId}); + + expect(resp).to.exist.and.to.be.a('array'); + expect(resp[0]).to.have.property('requestId', bid.bidId); + expect(resp[0]).to.have.property('cpm', 1); + expect(resp[0]).to.have.property('width', 320); + expect(resp[0]).to.have.property('height', 50); + expect(resp[0]).to.have.property('ad', '

I am an ad

'); + expect(resp[0]).to.have.property('ttl', 180); + expect(resp[0]).to.have.property('creativeId', 'CREATIVE_ID'); + expect(resp[0]).to.have.property('netRevenue', true); + expect(resp[0]).to.have.property('currency', 'EUR'); + expect(resp[0]).to.have.property('dealId', 'DEAL_ID'); + }); + it('No response', () => { + let bid = { + bidder: 'madvertise', + sizes: [[728, 90]], + bidId: '51ef8751f9aead', + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + auctionId: '18fd8b8b0bd757', + bidderRequestId: '418b37f85e772c', + params: { + s: 'test', + connection_type: 'WIFI', + age: 25, + } + }; + let resp = spec.interpretResponse({body: null}, {bidId: bid.bidId}); + + expect(resp).to.exist.and.to.be.a('array').that.is.empty; + }); + }); +}); diff --git a/test/spec/modules/mantisBidAdapter_spec.js b/test/spec/modules/mantisBidAdapter_spec.js index ac072aacccf..e2cd4df9a07 100644 --- a/test/spec/modules/mantisBidAdapter_spec.js +++ b/test/spec/modules/mantisBidAdapter_spec.js @@ -1,168 +1,178 @@ -'use strict'; - -describe('mantis adapter tests', function () { - const expect = require('chai').expect; - const Adapter = require('modules/mantisBidAdapter'); - const bidmanager = require('src/bidmanager'); - const adloader = require('src/adloader'); - const constants = require('src/constants.json'); - - var mantis, sandbox; - - beforeEach(() => { - mantis = new Adapter(); - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); +import {expect} from 'chai'; +import {spec} from 'modules/mantisBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('MantisAdapter', () => { + const adapter = newBidder(spec); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'mantis', + 'params': { + 'property': '10433394', + 'zone': 'zone' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); - delete window.context; - delete window.mantis_link; - delete window.mantis_breakpoint; - delete window.mantis_uuid; + 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); + }); }); - var callBidExample = { - bidderCode: 'mantis', - bids: [ + describe('buildRequests', () => { + let bidRequests = [ { - bidId: 'bidId1', - bidder: 'mantis', - placementCode: 'foo', - sizes: [[728, 90]], - params: { - property: '1234', - zoneId: 'zone1' - } - }, - { - bidId: 'bidId2', - bidder: 'mantis', - placementCode: 'bar', - sizes: [[300, 600], [300, 250]], - params: { - property: '1234', - zoneId: 'zone2' - } + 'bidder': 'mantis', + 'params': { + 'property': '10433394', + 'zone': 'zone' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', } - ] - }; - - describe('callBids', () => { - it('should create appropriate bid responses', () => { - sandbox.stub(bidmanager, 'addBidResponse'); - sandbox.stub(adloader, 'loadScript', function (url) { - var jsonp = eval(decodeURIComponent(url.match(/jsonp=(.*)&property/)[1])); - - jsonp({ - ads: { - bidId1: { - cpm: 1, - html: '', - width: 300, - height: 600 - } - } - }); - }); - - mantis.callBids(callBidExample); + ]; - sinon.assert.calledTwice(bidmanager.addBidResponse); + it('domain override', () => { + window.mantis_domain = 'http://foo'; + const request = spec.buildRequests(bidRequests); - expect(bidmanager.addBidResponse.firstCall.args[0]).to.eql('foo'); + expect(request.url).to.include('http://foo'); - var bid1 = bidmanager.addBidResponse.firstCall.args[1]; - expect(bid1.getStatusCode()).to.eql(constants.STATUS.GOOD); - expect(bid1.bidderCode).to.eql('mantis'); - expect(bid1.cpm).to.eql(1); - expect(bid1.ad).to.eql(''); - expect(bid1.width).to.eql(300); - expect(bid1.height).to.eql(600); + delete window.mantis_domain; + }); - expect(bidmanager.addBidResponse.secondCall.args[0]).to.eql('bar'); + it('standard request', () => { + const request = spec.buildRequests(bidRequests); - var bid2 = bidmanager.addBidResponse.secondCall.args[1]; - expect(bid2.getStatusCode()).to.eql(constants.STATUS.NO_BID); - expect(bid2.bidderCode).to.eql('mantis'); + expect(request.url).to.include('property=10433394'); + expect(request.url).to.include('bids[0][bidId]=30b31c1838de1e'); + expect(request.url).to.include('bids[0][config][zone]=zone'); + expect(request.url).to.include('bids[0][sizes][0][width]=300'); + expect(request.url).to.include('bids[0][sizes][0][height]=250'); + expect(request.url).to.include('bids[0][sizes][1][width]=300'); + expect(request.url).to.include('bids[0][sizes][1][height]=600'); }); - it('should load script with relevant bid data', () => { - sandbox.stub(adloader, 'loadScript'); - - mantis.callBids(callBidExample); - - sinon.assert.calledOnce(adloader.loadScript); - - var serverCall = adloader.loadScript.firstCall.args[0]; - - expect(serverCall).to.match(/buster=[0-9]+&/); - expect(serverCall).to.match(/tz=-?[0-9]+&/); - expect(serverCall).to.match(/secure=(true|false)&/); - expect(serverCall).to.string('property=1234&'); - expect(serverCall).to.string('bids[0][bidId]=bidId1&'); - expect(serverCall).to.string('bids[0][sizes][0][width]=728&'); - expect(serverCall).to.string('bids[0][sizes][0][height]=90&'); - expect(serverCall).to.string('bids[0][config][zoneId]=zone1&'); - expect(serverCall).to.string('bids[1][bidId]=bidId2&'); - expect(serverCall).to.string('bids[1][sizes][0][width]=300&'); - expect(serverCall).to.string('bids[1][sizes][0][height]=600&'); - expect(serverCall).to.string('bids[1][sizes][1][width]=300&'); - expect(serverCall).to.string('bids[1][sizes][1][height]=250&'); - expect(serverCall).to.string('bids[1][config][zoneId]=zone2&'); - expect(serverCall).to.string('version=1'); - }); + it('use window uuid', () => { + window.mantis_uuid = 'foo'; - /* tests below are to just adhere to code coverage requirements, but it is already tested in our own libraries/deployment process */ - it('should send uuid from window if set', () => { - sandbox.stub(adloader, 'loadScript'); + const request = spec.buildRequests(bidRequests); - window.mantis_uuid = '4321'; + expect(request.url).to.include('uuid=foo'); - mantis.callBids(callBidExample); + delete window.mantis_uuid; + }); + + it('use storage uuid', () => { + window.localStorage.setItem('mantis:uuid', 'bar'); - sinon.assert.calledOnce(adloader.loadScript); + const request = spec.buildRequests(bidRequests); - var serverCall = adloader.loadScript.firstCall.args[0]; + expect(request.url).to.include('uuid=bar'); - expect(serverCall).to.string('uuid=4321&'); + window.localStorage.removeItem('mantis:uuid'); }); - it('should send mobile = true if breakpoint is hit', () => { - sandbox.stub(adloader, 'loadScript'); + it('detect amp', () => { + var oldContext = window.context; - window.mantis_link = true; // causes iframe detection to not work - window.mantis_breakpoint = 100000000; // force everything to be mobile + window.context = {}; + window.context.tagName = 'AMP-AD'; + window.context.canonicalUrl = 'foo'; - mantis.callBids(callBidExample); + const request = spec.buildRequests(bidRequests); - sinon.assert.calledOnce(adloader.loadScript); + expect(request.url).to.include('amp=true'); + expect(request.url).to.include('url=foo'); - var serverCall = adloader.loadScript.firstCall.args[0]; + delete window.context.tagName; + delete window.context.canonicalUrl; - expect(serverCall).to.string('mobile=true&'); + window.context = oldContext; }); + }); + + describe('getUserSyncs', () => { + it('iframe', () => { + let result = spec.getUserSyncs({ + iframeEnabled: true + }); + + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.include('https://mantodea.mantisadnetwork.com/prebid/iframe'); + }); + + it('pixel', () => { + let result = spec.getUserSyncs({ + pixelEnabled: true + }); - it('should send different params if amp is detected', () => { - sandbox.stub(adloader, 'loadScript'); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.include('https://mantodea.mantisadnetwork.com/prebid/pixel'); + }); + }); - window.context = { - tagName: 'AMP-AD', - location: { - href: 'bar', - referrer: 'baz' + describe('interpretResponse', () => { + it('display ads returned', () => { + let response = { + body: { + ads: [ + { + bid: 'bid', + cpm: 1, + view: 'view', + width: 300, + height: 250, + html: '' + } + ] } }; - mantis.callBids(callBidExample); + let expectedResponse = [ + { + requestId: 'bid', + cpm: 1, + width: 300, + height: 250, + ttl: 86400, + ad: '', + creativeId: 'view', + netRevenue: true, + currency: 'USD' + } + ]; + let bidderRequest; - sinon.assert.calledOnce(adloader.loadScript); + let result = spec.interpretResponse(response, {bidderRequest}); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); - var serverCall = adloader.loadScript.firstCall.args[0]; + it('no ads returned', () => { + let response = { + body: { + ads: [] + } + }; + let bidderRequest; - expect(serverCall).to.string('mobile=true&'); - // expect(serverCall).to.string('url=bar&'); + let result = spec.interpretResponse(response, {bidderRequest}); + expect(result.length).to.equal(0); }); }); }); diff --git a/test/spec/modules/marsmediaBidAdapter_spec.js b/test/spec/modules/marsmediaBidAdapter_spec.js deleted file mode 100644 index c9381eb3c5f..00000000000 --- a/test/spec/modules/marsmediaBidAdapter_spec.js +++ /dev/null @@ -1,185 +0,0 @@ -import { expect } from 'chai'; -import * as ajax from 'src/ajax'; -import bidManager from 'src/bidmanager'; -import MarsmediaBidAdapter from '../../../modules/marsmediaBidAdapter'; -import CONSTANTS from 'src/constants.json'; -import adLoader from 'src/adloader'; - -describe('MarsMedia adapter implementation', () => { - let sandbox, - server, - marsmediaAdapter = new MarsmediaBidAdapter(), - BIDDER_REQUEST, - EMPTY_RESPONSE, - VALID_RESPONSE; - - beforeEach(() => { - BIDDER_REQUEST = { - bidderCode: 'marsmedia', - placementCode: 'div-1', - bids: [ - { - bidder: 'marsmedia', - params: { - publisherID: '1111', - floor: 0 - }, - sizes: [[320, 50]] - } - ] - }; - - EMPTY_RESPONSE = { - 'seatbid': [ - { - 'bid': [ - {} - ] - } - ], - 'bidid': '5616322932456153', - 'cur': 'USD' - }; - - VALID_RESPONSE = { - 'seatbid': [ - { - 'bid': [ - { - 'id': '1', - 'impid': '0c5b2f42-057b-0429-0694-0b42029af9e8', - 'price': 5, - 'adid': '11890', - 'nurl': 'http://ping-hq-2.rtbanalytics.com/bidder/ping_rtb.php?bid=3mhdom&wn=1&a_id=e7a96e1a-9777-5c48-41bc-91151c5b0b8e&gid=&r_id=9625963823905202&a_bp=5.0&a_p=${AUCTION_PRICE}&dcid=1&d=real1.rtbsrv.com&s_id=26&b_r_id=11890&v_id=0&a_pos=&u=5956487987&enp=uQ5qwrn5TQ&oapi=IzJ6W%3D%3DwN4kzN4QjN1kTN23bqB&oai=R2hHylhjYwIWNjFTNxETOtMmYxQTL4QzY10yN3cTOtEWMlZTOhdTZeXpeu&aname=asV6EXbmbP&abundle=ZywyWBnMaH&sdomain=0vQGB%3D%3DQbvNmL2J3ciRncuEDbhVmcKD4pf&spid=iPR8W%3D%3DwN4kzN4QjN1kTNKsMet&s_s_id=5956487987&dcarrier=HMjOzDJYic&city=G9diP6gJT7&uctm=1495112599131&b_id=306&cui=jYGqt%3D0SL8hqk6&hostn=bw7NZyEDbhVmc5j4VD&dspr=X2WmAw4CMCM32y', - 'adm': '', - 'adomain': ['wooga.com'], - 'iurl': 'http://feed-848915510.us-east-1.elb.amazonaws.com/banners/2290/jelly_splash/2861815_jelly-splash-iphone-app_android-app-install_creatives-jelly_320x50.jpg', - 'cid': '11890', - 'crid': '11890', - 'attr': [16] - } - ], - 'seat': '306' - } - ], - 'bidid': '9625963823905202', - 'cur': 'USD' - }; - - sandbox = sinon.sandbox.create(); - server = sinon.fakeServer.create(); - marsmediaAdapter = marsmediaAdapter.createNew(); - - sandbox.stub(bidManager, 'addBidResponse'); - }); - - afterEach(() => { - sandbox.restore(); - server.restore(); - }); - - describe('should receive a valid request bid -', () => { - it('no params', () => { - var bidder_request = BIDDER_REQUEST; - delete bidder_request.bids[0].params; - - expect(marsmediaAdapter.buildCallParams.bind(marsmediaAdapter, bidder_request.bids[0])).to.throw('No params'); - }); - - it('no sizes', () => { - var bidder_request = BIDDER_REQUEST; - delete bidder_request.bids[0].sizes; - - expect(marsmediaAdapter.buildCallParams.bind(marsmediaAdapter, bidder_request.bids[0])).to.throw('No sizes'); - }); - - it('no floor', () => { - var bidder_request = BIDDER_REQUEST; - delete bidder_request.bids[0].params.floor; - - expect(marsmediaAdapter.buildCallParams.bind(marsmediaAdapter, bidder_request.bids[0])).to.throw('No floor'); - }); - - it('floor should be number', () => { - var bidder_request = BIDDER_REQUEST; - bidder_request.bids[0].params.floor = 'str'; - - expect(marsmediaAdapter.buildCallParams.bind(marsmediaAdapter, bidder_request.bids[0])).to.throw('Floor must be numeric value'); - }); - }); - - describe('should receive a valid response -', () => { - it('error building call params', () => { - var request = marsmediaAdapter.buildCallParams(BIDDER_REQUEST.bids[0]); - - expect(request).that.is.an('string'); - - var request_obj = JSON.parse(request); - expect(request_obj).that.is.an('object'); - expect(request_obj).to.have.deep.property('id'); - expect(request_obj).to.have.deep.property('cur'); - - expect(request_obj).to.have.deep.property('imp'); - expect(request_obj['imp'][0]).to.have.deep.property('bidfloor'); - - expect(request_obj).to.have.deep.property('device'); - expect(request_obj).to.have.deep.property('user'); - expect(request_obj).to.have.deep.property('app'); - expect(request_obj).to.have.deep.property('publisher'); - }); - - it('error register bid', () => { - server.respondWith(JSON.stringify(VALID_RESPONSE)); - marsmediaAdapter.callBids(BIDDER_REQUEST); - server.respond(); - - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bidManager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - }); - }); - - describe('should handle bad response with - ', () => { - it('broken response', () => { - marsmediaAdapter.callBids(BIDDER_REQUEST); - - server.respondWith('{"id":'); - server.respond(); - - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bidManager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - }); - - it('empty response', () => { - marsmediaAdapter.callBids(BIDDER_REQUEST); - - server.respondWith('{}'); - server.respond(); - - expect(bidManager.addBidResponse.calledOnce).to.equal(true); - expect(bidManager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - }); - - it('empty bids', () => { - marsmediaAdapter.callBids(BIDDER_REQUEST); - - server.respondWith(JSON.stringify(EMPTY_RESPONSE)); - - server.respond(); - let response = JSON.parse(server.response[2]); - - expect(response).to.have.property('seatbid').that.is.an('array').with.lengthOf(1); - expect(response['seatbid'][0]).to.have.property('bid').to.be.lengthOf(1); - }); - - it('no adm', () => { - server.respondWith(JSON.stringify(VALID_RESPONSE)); - - server.respond(); - let response = JSON.parse(server.response[2]); - - expect(response).to.have.property('seatbid').that.is.an('array').with.lengthOf(1); - expect(response['seatbid'][0]).to.have.property('bid').to.be.lengthOf(1); - expect(response['seatbid'][0]['bid'][0]).to.have.property('adm'); - }); - }); -}); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js new file mode 100644 index 00000000000..520ec34fc7d --- /dev/null +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -0,0 +1,430 @@ +import {expect} from 'chai'; +import {spec} from 'modules/medianetBidAdapter'; +import { config } from 'src/config'; + +let VALID_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' + }, { + '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' + }], + VALID_BID_REQUEST_INVALID_BIDFLOOR = [{ + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'bidfloor': 'abcdef', + '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' + }, { + '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' + }], + VALID_AUCTIONDATA = { + 'timeout': config.getConfig('bidderTimeout'), + }, + VALID_PAYLOAD_INVALID_BIDFLOOR = { + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + }, + 'ext': { + 'customer_id': 'customer_id', + 'prebid_version': $$PREBID_GLOBAL$$.version + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [{ + 'id': '28f8f8130a583e', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-0' + }, + 'banner': [{ + 'w': 300, + 'h': 250 + }], + 'all': { + 'cid': 'customer_id', + 'bidfloor': 'abcdef', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + } + }, { + 'id': '3f97ca71b1e5c2', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-123' + }, + 'banner': [{ + 'w': 300, + 'h': 251 + }], + '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', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + }, + 'ext': { + 'customer_id': 'customer_id', + 'prebid_version': $$PREBID_GLOBAL$$.version + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [{ + 'id': '28f8f8130a583e', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-0' + }, + 'banner': [{ + 'w': 300, + 'h': 250 + }], + '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' + }, + 'banner': [{ + 'w': 300, + 'h': 251 + }], + 'all': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + } + }], + 'tmax': config.getConfig('bidderTimeout') + }, + VALID_PAYLOAD_PAGE_META = (() => { + let PAGE_META; + try { + PAGE_META = JSON.parse(JSON.stringify(VALID_PAYLOAD)); + } catch (e) {} + PAGE_META.site = Object.assign(PAGE_META.site, { + 'canonical_url': 'http://localhost:9999/canonical-test', + 'twitter_url': 'http://localhost:9999/twitter-test', + 'og_url': 'http://localhost:9999/fb-test' + }); + return PAGE_META; + })(), + VALID_PARAMS = { + bidder: 'medianet', + params: { + cid: '8CUV090' + } + }, + PARAMS_WITHOUT_CID = { + bidder: 'medianet', + params: {} + }, + PARAMS_WITH_INTEGER_CID = { + bidder: 'medianet', + params: { + cid: 8867587 + } + }, + PARAMS_WITH_EMPTY_CID = { + bidder: 'medianet', + params: { + cid: '' + } + }, + SYNC_OPTIONS_BOTH_ENABLED = { + iframeEnabled: true, + pixelEnabled: true, + }, + SYNC_OPTIONS_PIXEL_ENABLED = { + iframeEnabled: false, + pixelEnabled: true, + }, + SYNC_OPTIONS_IFRAME_ENABLED = { + iframeEnabled: true, + pixelEnabled: false, + }, + SERVER_CSYNC_RESPONSE = [{ + body: { + ext: { + csUrl: [{ + type: 'iframe', + url: 'iframe-url' + }, { + type: 'image', + url: 'pixel-url' + }] + } + } + }], + ENABLED_SYNC_IFRAME = [{ + type: 'iframe', + url: 'iframe-url' + }], + ENABLED_SYNC_PIXEL = [{ + type: 'image', + url: 'pixel-url' + }], + SERVER_RESPONSE_CPM_MISSING = { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': '375068987', + 'netRevenue': true + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + }, + SERVER_RESPONSE_CPM_ZERO = { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': '375068987', + 'netRevenue': true, + 'cpm': 0.0 + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + }, + SERVER_RESPONSE_NOBID = { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': true, + 'requestId': '3a62cf7a853f84', + 'width': 0, + 'height': 0, + 'ttl': 0, + 'netRevenue': false + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + }, + BID_REQUEST_SIZE_AS_1DARRAY = [{ + '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' + }, { + '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' + }]; + +describe('Media.net bid adapter', () => { + describe('isBidRequestValid', () => { + it('should accept valid bid params', () => { + let isValid = spec.isBidRequestValid(VALID_PARAMS); + expect(isValid).to.equal(true); + }); + + it('should reject bid if cid is not present', () => { + let isValid = spec.isBidRequestValid(PARAMS_WITHOUT_CID); + expect(isValid).to.equal(false); + }); + + it('should reject bid if cid is not a string', () => { + let isValid = spec.isBidRequestValid(PARAMS_WITH_INTEGER_CID); + expect(isValid).to.equal(false); + }); + + it('should reject bid if cid is a empty string', () => { + let isValid = spec.isBidRequestValid(PARAMS_WITH_EMPTY_CID); + expect(isValid).to.equal(false); + }); + }); + + describe('buildRequests', () => { + it('should build valid payload on bid', () => { + let requestObj = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA); + expect(JSON.parse(requestObj.data)).to.deep.equal(VALID_PAYLOAD); + }); + + it('should accept size as a one dimensional array', () => { + let bidReq = spec.buildRequests(BID_REQUEST_SIZE_AS_1DARRAY, VALID_AUCTIONDATA); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD); + }); + + it('should ignore bidfloor if not a valid number', () => { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_INVALID_BIDFLOOR, VALID_AUCTIONDATA); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_INVALID_BIDFLOOR); + }); + describe('build requests: when page meta-data is available', () => { + it('should pass canonical, twitter and fb paramters if available', () => { + let sandbox = sinon.sandbox.create(); + let documentStub = sandbox.stub(window.top.document, 'querySelector'); + documentStub.withArgs('link[rel="canonical"]').returns({ + href: 'http://localhost:9999/canonical-test' + }); + documentStub.withArgs('meta[property="og:url"]').returns({ + content: 'http://localhost:9999/fb-test' + }); + documentStub.withArgs('meta[name="twitter:url"]').returns({ + content: 'http://localhost:9999/twitter-test' + }); + let bidReq = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_PAGE_META); + sandbox.restore(); + }); + }); + }); + + describe('getUserSyncs', () => { + it('should exclude iframe syncs if iframe is disabled', () => { + let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_PIXEL_ENABLED, SERVER_CSYNC_RESPONSE); + expect(userSyncs).to.deep.equal(ENABLED_SYNC_PIXEL); + }); + + it('should exclude pixel syncs if pixel is disabled', () => { + let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_IFRAME_ENABLED, SERVER_CSYNC_RESPONSE); + expect(userSyncs).to.deep.equal(ENABLED_SYNC_IFRAME); + }); + + it('should choose iframe sync urls if both sync options are enabled', () => { + let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_BOTH_ENABLED, SERVER_CSYNC_RESPONSE); + expect(userSyncs).to.deep.equal(ENABLED_SYNC_IFRAME); + }); + }); + + describe('interpretResponse', () => { + it('should not push bid response if cpm missing', () => { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_CPM_MISSING, []); + expect(bids).to.deep.equal(validBids); + }); + + it('should not push bid response if cpm 0', () => { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_CPM_ZERO, []); + expect(bids).to.deep.equal(validBids); + }); + + it('should not push response if no-bid', () => { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_NOBID, []); + expect(bids).to.deep.equal(validBids) + }); + }); +}); diff --git a/test/spec/modules/memeglobalBidAdapter_spec.js b/test/spec/modules/memeglobalBidAdapter_spec.js deleted file mode 100644 index 0dc2d6f1541..00000000000 --- a/test/spec/modules/memeglobalBidAdapter_spec.js +++ /dev/null @@ -1,170 +0,0 @@ -describe('memeglobal adapter tests', function () { - const expect = require('chai').expect; - const adapter = require('modules/memeglobalBidAdapter'); - const bidmanager = require('src/bidmanager'); - const adLoader = require('src/adloader'); - var bidderName = 'memeglobal'; - - let stubLoadScript; - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - - afterEach(function () { - stubLoadScript.restore(); - }); - - function getBidSetForBidder() { - return $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === bidderName); - } - - function checkBidsRequestedInit() { - var bidSet = getBidSetForBidder(); - if (!bidSet) { - var bidderRequest = { - start: null, - requestId: null, - bidder: 'memeglobal', - bidderCode: 'memeglobal', - bids: [] - }; - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - } - } - - describe('functions and initialization', function () { - it('should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.mgres).to.exist.and.to.be.a('function'); - }); - - it('callBids with params', function () { - var params = { - bidderCode: 'memeglobal', - bidder: 'memeglobal', - bids: [{ - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'memeglobal', - params: { siteId: '3608', adSizes: '300x250' }, - requestId: '10b327aa396609', - placementCode: 'header-bid-tag-0' - } - ] - }; - - adapter().callBids(params); - sinon.assert.calledOnce(stubLoadScript); - }); - - it('callBids empty params', function () { - var params = { - bidderCode: 'memeglobal', - bidder: 'memeglobal', - bids: [{ - bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], - bidder: 'memeglobal', - params: { siteId: '3608', adSizes: '300x250' }, - requestId: '10b327aa396609', - placementCode: 'header-bid-tag-0' - } - ] - }; - - adapter().callBids({}); - expect(stubLoadScript.callCount).to.equal(0); - }); - }); - - describe('memeglobalResponse', function () { - it('should not add bid responses if no bids returned', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - checkBidsRequestedInit(); - - var bid = { - bidId: 'bidId1', - bidder: 'memeglobal', - params: { - tagid: '007' - }, - sizes: [[300, 250]], - placementCode: 'test-1' - } - // no bids returned in the response. - var response = { - 'id': '54321', - 'seatbid': [] - }; - var bidSet = getBidSetForBidder(); - bidSet.bids.push(bid); - - // adapter needs to be called for stub registration. - adapter() - - $$PREBID_GLOBAL$$.mgres(response); - - expect(stubAddBidResponse.getCall(0)).to.equal(null); - // var bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; - // expect(bidPlacementCode).to.equal('test-1'); - // - // var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - // expect(bidObject1.getStatusCode()).to.equal(2); - // expect(bidObject1.bidderCode).to.equal('memeglobal'); - - stubAddBidResponse.calledThrice; - stubAddBidResponse.restore(); - }); - - it('should add a bid response for bids returned and empty bid responses for the rest', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - checkBidsRequestedInit(); - - var bid = { - bidId: 'bidId2', - bidder: 'memeglobal', - params: { - tagid: '315045' - }, - sizes: [[320, 50]], - placementCode: 'test-2' - }; - - // Returning a single bid in the response. - var response = { - 'id': '54321111', - 'seatbid': [ { - 'bid': [ { - 'id': '1111111', - 'impid': 'bidId2', - 'price': 0.09, - 'nurl': 'http://url', - 'adm': 'ad-code', - 'h': 250, - 'w': 300, - 'ext': { } - } ] - } ] - }; - - var bidSet = getBidSetForBidder(); - bidSet.bids.push(bid); - adapter() - $$PREBID_GLOBAL$$.mgres(response); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidPlacementCode1).to.equal('test-2'); - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.bidderCode).to.equal('memeglobal'); - expect(bidObject1.creative_id).to.equal('1111111'); - expect(bidObject1.cpm).to.equal(0.09); - expect(bidObject1.height).to.equal(250); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.ad).to.equal('ad-code'); - - stubAddBidResponse.calledThrice; - - stubAddBidResponse.restore(); - }); - }); -}); diff --git a/test/spec/modules/mobfoxBidAdapter_spec.js b/test/spec/modules/mobfoxBidAdapter_spec.js index f2819009908..54a057991e3 100644 --- a/test/spec/modules/mobfoxBidAdapter_spec.js +++ b/test/spec/modules/mobfoxBidAdapter_spec.js @@ -1,162 +1,123 @@ -describe('mobfox adapter tests', function () { +describe('mobfox adapter tests', () => { const expect = require('chai').expect; const utils = require('src/utils'); const adapter = require('modules/mobfoxBidAdapter'); - const bidmanager = require('src/bidmanager'); - const adloader = require('src/adloader'); - const CONSTANTS = require('src/constants.json'); - const ajax = require('src/ajax.js'); - let mockResponses = { - banner: { - 'request': { - 'type': 'textAd', - 'htmlString': '<\/title><style>body{margin:0;padding:0}#mobfoxCover{background:0 0;margin:0;padding:0;border:none;position:absolute;left:0;top:0;z-index:100}<\/style><\/head><body><div id="mobfoxCover"><\/div><script type="text\/javascript">function checkRedirect(e){return function(){if(state===REDIRECT){state=REDUNDANT;var t=window.document.querySelector("iframe").contentDocument.querySelector("html").innerHTML.toLowerCase();if(!(t.indexOf("<script")<0&&t.indexOf("<iframe")<0)){var o=new XMLHttpRequest,d={creativeId:creativeId,advertiserId:advertiserId,hParam:hParam,dspId:dspId,networkId:networkId,autoPilotInventoryConfId:autoPilotInventoryConfId,stackItemId:stackItemId,adSpaceId:adSpaceId,cId:cId,adomain:adomain,geo:geo,event:e,ua:window.navigator.userAgent,adId:adId,site:window.location.href,md5Hash:md5Hash,snapshot:btoa(unescape(encodeURIComponent(t)))};o.open("POST","http:\/\/my.mobfox.com\/fraud-integration",!1),o.setRequestHeader("Content-type","application\/json"),o.send(JSON.stringify(d))}}}}function init(){window.onbeforeunload=checkRedirect("onbeforeunload"),window.addEventListener("beforeunload",checkRedirect("beforeunload")),window.addEventListener("unload",checkRedirect("unload")),document.addEventListener("visibilitychange",function(){"hidden"===document.visibilityState&&checkRedirect("visibilityState")});var e=document.createElement("iframe");document.body.appendChild(e),e.width="320",e.height="50";var t=document.querySelector("#mobfoxCover");t.style.width=e.width+"px",t.style.height=e.height+"px",e.style.margin="0px",e.style.padding="0px",e.style.border="none",e.scrolling="no",e.style.overflow="hidden",e.sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation allow-same-origin";var o=atob(markupB64);setTimeout(function(){state=NORMAL},200),setTimeout(function(){var e=document.querySelector("#mobfoxCover");document.body.removeChild(e)},200);var d="srcdoc"in e,n=o;o.indexOf("<body>")<0&&(n="<html><body style="margin:0">"+o+"<\/body><\/html>"),d?e.srcdoc=n:(e.contentWindow.document.open(),e.contentWindow.document.write(n),e.contentWindow.document.close())}var markupB64="PGEgaHJlZj0iaHR0cDovL3Rva3lvLW15Lm1vYmZveC5jb20vZXhjaGFuZ2UuY2xpY2sucGhwP2g9ZGI1ZjZkOTJiMDk1OGI0ZDFlNjU4ZjZlNWRkNWY0MmUiIHRhcmdldD0iX2JsYW5rIj48aW1nIHNyYz0iaHR0cHM6Ly9jcmVhdGl2ZWNkbi5tb2Jmb3guY29tL2U4ZTkxNWYzMmJhOTVkM2JmMzY4YTM5N2EyMzQ4NzVmLmdpZiIgYm9yZGVyPSIwIi8+PC9hPjxicj48aW1nIHN0eWxlPSJwb3NpdGlvbjphYnNvbHV0ZTsgbGVmdDogLTEwMDAwcHg7IiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBzcmM9Imh0dHA6Ly90b2t5by1teS5tb2Jmb3guY29tL2V4Y2hhbmdlLnBpeGVsLnBocD9oPWRiNWY2ZDkyYjA5NThiNGQxZTY1OGY2ZTVkZDVmNDJlIi8+PHNjcmlwdCB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiPmRvY3VtZW50LndyaXRlKCc8aW1nIHN0eWxlPSJwb3NpdGlvbjphYnNvbHV0ZTsgbGVmdDogLTEwMDAwcHg7IiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBzcmM9Imh0dHA6Ly90b2t5by1teS5tb2Jmb3guY29tL2V4Y2hhbmdlLnBpeGVsLnBocD9oPWRiNWY2ZDkyYjA5NThiNGQxZTY1OGY2ZTVkZDVmNDJlJnRlc3Q9MSIvPicpOzwvc2NyaXB0Pg==",INITIAL=0,REDIRECT=1,REDUNDANT=2,NORMAL=3,state=INITIAL,creativeId="",advertiserId="",hParam="db5f6d92b0958b4d1e658f6e5dd5f42e",dspId="",networkId="",autoPilotInventoryConfId="",stackItemId="392746",serverHost="184.172.209.50",adSpaceId="",adId="",cId="",adomain="",geo="US",md5Hash="f3bd183c0b19faf12c76e75461cb8cac";document.addEventListener("DOMContentLoaded",function(e){state=REDIRECT}),setTimeout(init,1)<\/script><\/body><\/html>', - 'clicktype': 'safari', - 'clickurl': 'http://tokyo-my.mobfox.com/exchange.click.php?h=db5f6d92b0958b4d1e658f6e5dd5f42e', - 'urltype': 'link', - 'refresh': '30', - 'scale': 'no', - 'skippreflight': 'yes' - } - } - }; - - let mockRequestsParams = { - banner: { - rt: 'api', - r_type: 'banner', - i: '69.197.148.18', - s: 'fe96717d9875b9da4339ea5367eff1ec', - u: 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4', - adspace_strict: 0, - - // o_iosadvid: '1976F519-26D0-4428-9891-3133253A453F', - // r_floor: '0.2', - // longitude: '12.12', - // latitude: '280.12', - // demo_gender: 'male', - // demo_age: '1982', - // demo_keywords: 'sports', - // adspace_width: 320, - // adspace_height: 50 - } - }; - before(() => sinon.stub(document.body, 'appendChild')); - after(() => document.body.appendChild.restore()); - - let xhrMock = { - getResponseHeader: getResponseHeaderMock - }; - function getResponseHeaderMock(header) { - switch (header) { - case 'Content-Type': - return 'application/json'; - case 'X-Pricing-CPM': - return '1'; - } - } - function createMobfoxErrorStub() { - return sinon.stub(ajax, 'ajax', (url, callbacks) => { - callbacks.success( - JSON.stringify({error: 'No Ad Available'}), - xhrMock - ); + const bidRequest = [{ + code: 'div-gpt-ad-1460505748561-0', + sizes: [[320, 480], [300, 250], [300, 600]], + // Replace this object to test a new Adapter! + bidder: 'mobfox', + bidId: '5t5t5t5', + params: { + s: '267d72ac3f77a3f447b32cf7ebf20673', // required - The hash of your inventory to identify which app is making the request, + imp_instl: 1 // optional - set to 1 if using interstitial otherwise delete or set to 0 + }, + placementCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'c241c810-18d9-4aa4-a62f-8c1980d8d36b', + transactionId: '31f42cba-5920-4e47-adad-69c79d0d4fb4' + }]; + + describe('validRequests', () => { + let bidRequestInvalid1 = [{ + code: 'div-gpt-ad-1460505748561-0', + sizes: [[320, 480], [300, 250], [300, 600]], + // Replace this object to test a new Adapter! + bidder: 'mobfox', + bidId: '5t5t5t5', + params: { + imp_instl: 1 // optional - set to 1 if using interstitial otherwise delete or set to 0 + }, + placementCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'c241c810-18d9-4aa4-a62f-8c1980d8d36b', + transactionId: '31f42cba-5920-4e47-adad-69c79d0d4fb4' + }]; + + it('test valid MF request success', () => { + let isValid = adapter.spec.isBidRequestValid(bidRequest[0]); + expect(isValid).to.equal(true); }); - } - function createMobfoxSuccessStub() { - return sinon.stub(ajax, 'ajax', (url, callbacks) => { - callbacks.success( - JSON.stringify(mockResponses.banner) - , xhrMock - ); + it('test valid MF request failed1', () => { + let isValid = adapter.spec.isBidRequestValid(bidRequestInvalid1[0]); + expect(isValid).to.equal(false); }); - } - - describe('test mobfox error response', function () { - let stubAddBidResponse, stubAjax; - before(function () { - stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - stubAjax = createMobfoxErrorStub() + }) + + describe('buildRequests', () => { + it('test build MF request', () => { + let request = adapter.spec.buildRequests(bidRequest); + let payload = request.data.split('&'); + expect(payload[0]).to.equal('rt=api-fetchip'); + expect(payload[1]).to.equal('r_type=banner'); + expect(payload[2]).to.equal('r_resp=json'); + expect(payload[3]).to.equal('s=267d72ac3f77a3f447b32cf7ebf20673'); + expect(payload[5]).to.equal('adspace_width=320'); + expect(payload[6]).to.equal('adspace_height=480'); + expect(payload[7]).to.equal('imp_instl=1'); }); - after(function () { - stubAddBidResponse.restore(); - stubAjax.restore(); + it('test build MF request', () => { + let request = adapter.spec.buildRequests(bidRequest); + let payload = request.data.split('&'); + expect(payload[0]).to.equal('rt=api-fetchip'); + expect(payload[1]).to.equal('r_type=banner'); + expect(payload[2]).to.equal('r_resp=json'); + expect(payload[3]).to.equal('s=267d72ac3f77a3f447b32cf7ebf20673'); + expect(payload[5]).to.equal('adspace_width=320'); + expect(payload[6]).to.equal('adspace_height=480'); + expect(payload[7]).to.equal('imp_instl=1'); }); - - it('should add empty bid responses if no bids returned', function () { - let bidderRequest = { - bidderCode: 'mobfox', - bids: [ - { - bidId: 'bidId1', - bidder: 'mobfox', - params: {}, - sizes: [[300, 250]], - placementCode: 'test-gpt-div-1234' + }) + + describe('interceptResponse', () => { + let mockServerResponse = { + body: { + request: { + clicktype: 'safari', + clickurl: 'http://tokyo-my.mobfox.com/exchange.click.php?h=494ef76d5b0287a8b5ac8724855cb5e0', + cpmPrice: 50, + htmlString: 'test', + refresh: '30', + scale: 'no', + skippreflight: 'yes', + type: 'textAd', + urltype: 'link' + } + }, + headers: { + get: function (header) { + if (header === 'X-Pricing-CPM') { + return 50; } - ] - }; - - // empty ads in bidresponse - let requestParams = utils.cloneJson(mockRequestsParams.banner); - requestParams.adspace_width = 1231564; // should return an error - bidderRequest.bids[0].params = requestParams; - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - // adapter needs to be called, in order for the stub to register. - adapter().callBids(bidderRequest); - - let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidPlacementCode1).to.equal('test-gpt-div-1234'); - expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(bidResponse1.bidderCode).to.equal('mobfox'); - }); - }); - - describe('test mobfox success response', function () { - let stubAddBidResponse, stubAjax; - before(function () { - stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - stubAjax = createMobfoxSuccessStub() - }); - - after(function () { - stubAddBidResponse.restore(); - stubAjax.restore(); + } + } + }; + it('test intercept response', () => { + let request = adapter.spec.buildRequests(bidRequest); + let bidResponses = adapter.spec.interpretResponse(mockServerResponse, request); + expect(bidResponses.length).to.equal(1); + expect(bidResponses[0].ad).to.equal('test'); + expect(bidResponses[0].cpm).to.equal(50); + expect(bidResponses[0].creativeId).to.equal('267d72ac3f77a3f447b32cf7ebf20673'); + expect(bidResponses[0].requestId).to.equal('5t5t5t5'); + expect(bidResponses[0].currency).to.equal('USD'); + expect(bidResponses[0].height).to.equal('480'); + expect(bidResponses[0].netRevenue).to.equal(true); + expect(bidResponses[0].referrer).to.equal('http://tokyo-my.mobfox.com/exchange.click.php?h=494ef76d5b0287a8b5ac8724855cb5e0'); + expect(bidResponses[0].ttl).to.equal(360); + expect(bidResponses[0].width).to.equal('320'); }); - it('should add a bid response', function () { - let bidderRequest = { - bidderCode: 'mobfox', - bids: [ - { - bidId: 'bidId1', - bidder: 'mobfox', - params: {}, - sizes: [[300, 250]], - placementCode: 'test-gpt-div-1234' - } - ] + it('test intercept response with empty server response', () => { + let request = adapter.spec.buildRequests(bidRequest); + let serverResponse = { + request: { + error: 'cannot get response' + } }; - - let requestParams = utils.cloneJson(mockRequestsParams.banner); - bidderRequest.bids[0].params = requestParams; - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - // adapter needs to be called, in order for the stub to register. - adapter().callBids(bidderRequest); - - let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidPlacementCode1).to.equal('test-gpt-div-1234'); - expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidResponse1.bidderCode).to.equal('mobfox'); - - expect(bidResponse1.cpm).to.equal(1); - expect(bidResponse1.width).to.equal(bidderRequest.bids[0].sizes[0][0]); - expect(bidResponse1.height).to.equal(bidderRequest.bids[0].sizes[0][1]); - }); - }); + let bidResponses = adapter.spec.interpretResponse(serverResponse, request); + expect(bidResponses.length).to.equal(0); + }) + }) }); diff --git a/test/spec/modules/nanointeractiveBidAdapter_spec.js b/test/spec/modules/nanointeractiveBidAdapter_spec.js new file mode 100644 index 00000000000..92b6fe8d797 --- /dev/null +++ b/test/spec/modules/nanointeractiveBidAdapter_spec.js @@ -0,0 +1,110 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils'; + +import { + BIDDER_CODE, CATEGORY, DATA_PARTNER_PIXEL_ID, ENGINE_BASE_URL, NQ, NQ_NAME, SUB_ID, + spec +} from '../../../modules/nanointeractiveBidAdapter'; + +describe('nanointeractive adapter tests', function () { + const SEARCH_QUERY = 'rumpelstiltskin'; + const WIDTH = 300; + const HEIGHT = 250; + const SIZES = [[WIDTH, HEIGHT]]; + const AD = '<script type="text/javascript" src="https://trc.audiencemanager.de/ad/?pl=58c2829beb0a193456047a27&cb=${CACHEBUSTER}&tc=${CLICK_URL_ENC}"></script> <noscript> <a href="https://trc.audiencemanager.de/ad/?t=c&pl=58c2829beb0a193456047a27&cb=${CACHEBUSTER}&tc=${CLICK_URL_ENC}"> <img src="https://trc.audiencemanager.de/ad/?t=i&pl=58c2829beb0a193456047a27&cb=${CACHEBUSTER}" alt="Click Here" border="0"> </a> </noscript>'; + const CPM = 1; + + function getBid(isValid) { + return { + bidder: BIDDER_CODE, + params: (function () { + return { + [DATA_PARTNER_PIXEL_ID]: isValid === true ? 'pid1' : null, + [NQ]: SEARCH_QUERY, + [NQ_NAME]: null, + [CATEGORY]: null, + [SUB_ID]: null, + } + })(), + placementCode: 'div-gpt-ad-1460505748561-0', + transactionId: 'ee335735-ddd3-41f2-b6c6-e8aa99f81c0f', + sizes: SIZES, + bidId: '24a1c9ec270973', + bidderRequestId: '189135372acd55', + auctionId: 'ac15bb68-4ef0-477f-93f4-de91c47f00a9' + } + } + + const SINGLE_BID_REQUEST = { + [DATA_PARTNER_PIXEL_ID]: 'pid1', + [NQ]: [SEARCH_QUERY, null], + [SUB_ID]: null, + sizes: [WIDTH + 'x' + HEIGHT], + bidId: '24a1c9ec270973', + cors: 'http://localhost' + }; + + function getSingleBidResponse(isValid) { + return { + id: '24a1c9ec270973', + cpm: isValid === true ? CPM : null, + width: WIDTH, + height: HEIGHT, + ad: AD, + ttl: 360, + creativeId: 'TEST_ID', + netRevenue: false, + currency: 'EUR', + } + } + + const VALID_BID = { + requestId: '24a1c9ec270973', + cpm: CPM, + width: WIDTH, + height: HEIGHT, + ad: AD, + ttl: 360, + creativeId: 'TEST_ID', + netRevenue: false, + currency: 'EUR', + }; + + describe('NanoAdapter', () => { + let nanoBidAdapter = spec; + + describe('Methods', () => { + it('Test isBidRequestValid() with valid param', function () { + expect(nanoBidAdapter.isBidRequestValid(getBid(true))).to.equal(true); + }); + it('Test isBidRequestValid() with invalid param', function () { + expect(nanoBidAdapter.isBidRequestValid(getBid(false))).to.equal(false); + }); + it('Test buildRequests()', function () { + let stub = sinon.stub(utils, 'getOrigin').callsFake(() => 'http://localhost'); + + let request = nanoBidAdapter.buildRequests([getBid(true)]); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENGINE_BASE_URL); + expect(request.data).to.equal(JSON.stringify([SINGLE_BID_REQUEST])); + + stub.restore(); + }); + it('Test interpretResponse() length', function () { + let bids = nanoBidAdapter.interpretResponse({body: [getSingleBidResponse(true), getSingleBidResponse(false)]}); + expect(bids.length).to.equal(1); + }); + it('Test interpretResponse() bids', function () { + let bid = nanoBidAdapter.interpretResponse({body: [getSingleBidResponse(true), getSingleBidResponse(false)]})[0]; + expect(bid.requestId).to.equal(VALID_BID.requestId); + expect(bid.cpm).to.equal(VALID_BID.cpm); + expect(bid.width).to.equal(VALID_BID.width); + expect(bid.height).to.equal(VALID_BID.height); + expect(bid.ad).to.equal(VALID_BID.ad); + expect(bid.ttl).to.equal(VALID_BID.ttl); + expect(bid.creativeId).to.equal(VALID_BID.creativeId); + expect(bid.currency).to.equal(VALID_BID.currency); + }); + }); + }); +}); diff --git a/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js b/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js new file mode 100644 index 00000000000..7ed65718657 --- /dev/null +++ b/test/spec/modules/nasmediaAdmixerBidAdapter_spec.js @@ -0,0 +1,138 @@ +import {expect} from 'chai'; +import {spec} from 'modules/nasmediaAdmixerBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('nasmediaAdmixerBidAdapter', () => { + 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', () => { + const bid = { + 'bidder': 'nasmediaAdmixer', + 'params': { + 'ax_key': 'ax_key' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '3361d01e67dbd6', + 'bidderRequestId': '2b60dcd392628a', + 'auctionId': '124cb070528662', + }; + + 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', () => { + const bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'ax_key': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const bidRequests = [ + { + 'bidder': 'nasmediaAdmixer', + 'params': { + 'ax_key': 'ajj7jba3' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '3361d01e67dbd6', + 'bidderRequestId': '2b60dcd392628a', + 'auctionId': '124cb070528662', + } + ]; + + it('sends bid request to url via GET', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.method).to.equal('GET'); + expect(request.url).to.match(new RegExp(`https://adn.admixer.co.kr`)); + }); + }); + + describe('interpretResponse', () => { + const response = { + 'body': { + 'bidder': 'nasmedia_admixer', + 'req_id': '861a8e7952c82c', + 'error_code': 0, + 'error_msg': 'OK', + 'body': [{ + 'ad_id': '20049', + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'cpm': 1.769221, + 'ad': '<!-- Creative -->' + }] + }, + 'headers': { + 'get': function () { + } + } + }; + + const bidRequest = { + 'bidder': 'nasmediaAdmixer', + 'params': { + 'ax_key': 'ajj7jba3', + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [320, 480]], + 'bidId': '31300c8b9697cd', + 'bidderRequestId': '2bf570adcf83fa', + 'auctionId': '169827a33f03cc', + }; + + it('should get correct bid response', () => { + const expectedResponse = [ + { + 'requestId': '861a8e7952c82c', + 'cpm': 1.769221, + 'currency': 'USD', + 'width': 300, + 'height': 250, + 'ad': '<!-- Creative -->', + 'creativeId': '20049', + 'ttl': 360, + 'netRevenue': false + } + ]; + + const result = spec.interpretResponse(response, bidRequest); + expect(result).to.have.lengthOf(1); + let resultKeys = Object.keys(result[0]); + expect(resultKeys.sort()).to.deep.equal(Object.keys(expectedResponse[0]).sort()); + resultKeys.forEach(function (k) { + if (k === 'ad') { + expect(result[0][k]).to.match(/<!-- Creative -->$/); + } else { + expect(result[0][k]).to.equal(expectedResponse[0][k]); + } + }); + }); + + it('handles nobid responses', () => { + response.body = { + 'bidder': 'nasmedia_admixer', + 'req_id': '861a8e7952c82c', + 'error_code': 0, + 'error_msg': 'OK', + 'body': [] + }; + + const result = spec.interpretResponse(response, bidRequest); + expect(result).to.have.lengthOf(0); + }); + }); +}); diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js new file mode 100644 index 00000000000..3d7bba417f9 --- /dev/null +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -0,0 +1,135 @@ +import { expect } from 'chai'; +import { spec } from 'modules/oneVideoBidAdapter'; +import * as utils from 'src/utils'; + +describe('OneVideoBidAdapter', () => { + let bidRequest; + + beforeEach(() => { + bidRequest = { + bidder: 'oneVideo', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2] + }, + site: { + id: 1, + page: 'https://news.yahoo.com/portfolios', + referrer: 'http://www.yahoo.com' + }, + pubId: 'brxd' + } + }; + }); + + describe('spec.isBidRequestValid', () => { + it('should return true when the required params are passed', () => { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false when the "video" param is missing', () => { + bidRequest.params = { + pubId: 'brxd' + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when the "pubId" param is missing', () => { + bidRequest.params = { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when no bid params are passed', () => { + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('spec.buildRequests', () => { + it('should create a POST request for every bid', () => { + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(location.protocol + spec.ENDPOINT + bidRequest.params.pubId); + }); + + it('should attach the bid request object', () => { + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].bidRequest).to.equal(bidRequest); + }); + + it('should attach request data', () => { + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + const [ width, height ] = bidRequest.sizes; + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); + }); + + it('must parse bid size from a nested array', () => { + const width = 640; + const height = 480; + bidRequest.sizes = [[ width, height ]]; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + }); + }); + + describe('spec.interpretResponse', () => { + it('should return no bids if the response is not valid', () => { + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "nurl" and "adm" are missing', () => { + const serverResponse = {seatbid: [{bid: [{price: 6.01}]}]}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "price" is missing', () => { + const serverResponse = {seatbid: [{bid: [{adm: '<VAST></VAST>'}]}]}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid bid response with just "adm"', () => { + const serverResponse = {seatbid: [{bid: [{id: 1, price: 6.01, adm: '<VAST></VAST>'}]}], 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, + vastXml: serverResponse.seatbid[0].bid[0].adm, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 100, + netRevenue: true + }; + expect(bidResponse).to.deep.equal(o); + }); + }); +}); diff --git a/test/spec/modules/oneplanetonlyBidAdapter_spec.js b/test/spec/modules/oneplanetonlyBidAdapter_spec.js new file mode 100644 index 00000000000..4a42b471b6f --- /dev/null +++ b/test/spec/modules/oneplanetonlyBidAdapter_spec.js @@ -0,0 +1,100 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/oneplanetonlyBidAdapter'; + +describe('OnePlanetOnlyAdapter', () => { + let bid = { + bidId: '51ef8751f9aead', + bidder: 'oneplanetonly', + params: { + siteId: '5', + adUnitId: '5-4587544', + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + sizes: [[300, 250], [300, 600]], + bidderRequestId: '418b37f85e772c', + auctionId: '18fd8b8b0bd757' + }; + + describe('isBidRequestValid', () => { + it('Should return true if there are params.siteId and params.adUnitId parameters present', () => { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', () => { + delete bid.params.adUnitId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', () => { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', () => { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', () => { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', () => { + expect(serverRequest.url).to.equal('//show.oneplanetonly.com/prebid?siteId=5'); + }); + it('Returns valid data if array of bids is valid', () => { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('id', 'ver', 'prebidVer', 'transactionId', 'currency', 'timeout', 'siteId', + 'domain', 'page', 'referrer', 'adUnits'); + + let adUnit = data.adUnits[0]; + expect(adUnit).to.have.keys('id', 'bidId', 'sizes'); + expect(adUnit.id).to.equal('5-4587544'); + expect(adUnit.bidId).to.equal('51ef8751f9aead'); + expect(adUnit.sizes).to.have.members(['300x250', '300x600']); + }); + it('Returns empty data if no valid requests are passed', () => { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.adUnits).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', () => { + it('Should interpret banner response', () => { + const serverResponse = { + body: { + bids: [{ + requestId: '51ef8751f9aead', + cpm: 0.4, + width: 300, + height: 250, + creativeId: '2', + currency: 'USD', + ad: 'Test', + ttl: 120, + }] + } + }; + let bannerResponses = spec.interpretResponse(serverResponse); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let bidObject = bannerResponses[0]; + expect(bidObject).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency'); + expect(bidObject.requestId).to.equal('51ef8751f9aead'); + expect(bidObject.cpm).to.equal(0.4); + expect(bidObject.width).to.equal(300); + expect(bidObject.height).to.equal(250); + expect(bidObject.ad).to.equal('Test'); + expect(bidObject.ttl).to.equal(120); + expect(bidObject.creativeId).to.equal('2'); + expect(bidObject.netRevenue).to.be.true; + expect(bidObject.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid response is passed', () => { + const invalid = { + body: {} + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js new file mode 100644 index 00000000000..dfd3254fd75 --- /dev/null +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -0,0 +1,129 @@ +import { spec } from 'modules/onetagBidAdapter'; +import { expect } from 'chai'; + +describe('onetag', () => { + let bid = { + 'bidder': 'onetag', + 'params': { + 'pubId': '386276e072', + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': 'qwerty123' + }; + + describe('isBidRequestValid', () => { + it('Should return true when required params are found', () => { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when pubId is not a string', () => { + bid.params.pubId = 30; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('Should return false when pubId is undefined', () => { + bid.params.pubId = undefined; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('Should return false when the sizes array is empty', () => { + bid.sizes = []; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', () => { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', () => { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', () => { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', () => { + expect(serverRequest.url).to.equal('https://onetag-sys.com/prebid-request'); + }); + + const d = serverRequest.data; + try { + const data = JSON.parse(d); + it('Should contains all keys', () => { + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('location', 'masked', 'referrer', 'sHeight', 'sWidth', 'timeOffset', 'date', 'wHeight', 'wWidth', 'bids'); + expect(data.location).to.be.a('string'); + expect(data.masked).to.be.a('number'); + expect(data.referrer).to.be.a('string'); + expect(data.sHeight).to.be.a('number'); + expect(data.sWidth).to.be.a('number'); + expect(data.wWidth).to.be.a('number'); + expect(data.wHeight).to.be.a('number'); + expect(data.timeOffset).to.be.a('number'); + expect(data.date).to.be.a('string'); + expect(data.bids).to.be.an('array'); + + const bids = data['bids']; + for (let i = 0; i < bids.length; i++) { + const bid = bids[i]; + expect(bid).to.have.all.keys('adUnitCode', 'auctionId', 'bidId', 'bidderRequestId', 'pubId', 'transactionId', 'sizes'); + expect(bid.bidId).to.be.a('string'); + expect(bid.pubId).to.be.a('string'); + } + }); + } catch (e) { + console.log('Error while parsing'); + } + it('Returns empty data if no valid requests are passed', () => { + serverRequest = spec.buildRequests([]); + let dataString = serverRequest.data; + try { + let dataObj = JSON.parse(dataString); + expect(dataObj.bids).to.be.an('array').that.is.empty; + } catch (e) { + console.log('Error while parsing'); + } + }); + }); + describe('interpretResponse', () => { + const resObject = { + body: { + nobid: false, + bids: [{ + ad: '<div>Advertising</div>', + cpm: 13, + width: 300, + height: 250, + creativeId: '1820', + dealId: 'dishfo', + currency: 'USD', + requestId: 'sdiceobxcw' + }] + } + }; + it('Returns an array of valid server responses if response object is valid', () => { + const serverResponses = spec.interpretResponse(resObject); + + 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', 'dealId'); + 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'); + } + it('Returns an empty array if invalid response is passed', () => { + const serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); +}); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index c08e8c256e6..3585987e045 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,277 +1,1172 @@ -const expect = require('chai').expect; -const assert = require('chai').assert; -const adapter = require('modules/openxBidAdapter')(); -const bidmanager = require('src/bidmanager'); -const adloader = require('src/adloader'); -const CONSTANTS = require('src/constants.json'); -const ajax = require('src/ajax'); - -describe('openx adapter tests', function () { - describe('test openx callback response', function () { - let stubAjax; - let stubAddBidResponse; - this.response = null; - let responseHandlerCallback = (_url, callback, _data, _params) => { - return callback(this.response); - }; +import {expect} from 'chai'; +import {spec, resetBoPixel} from 'modules/openxBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import {userSync} from 'src/userSync'; +import {config} from 'src/config'; +import * as utils from 'src/utils'; - beforeEach(() => { - stubAjax = sinon.stub(ajax, 'ajax', responseHandlerCallback); - stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - sinon.stub(document.body, 'appendChild'); - }); - afterEach(() => { - stubAjax.restore(); - stubAddBidResponse.restore(); - this.response = null; - document.body.appendChild.restore(); +const URLBASE = '/w/1.0/arj'; +const URLBASEVIDEO = '/v/1.0/avjp'; + +describe('OpenxAdapter', () => { + const adapter = newBidder(spec); + + /** + * Type Definitions + */ + + /** + * @typedef {{ + * impression: string, + * inview: string, + * click: string + * }} + */ + let OxArjTracking; + /** + * @typedef {{ + * ads: { + * version: number, + * count: number, + * pixels: string, + * ad: Array<OxArjAdUnit> + * } + * }} + */ + let OxArjResponse; + /** + * @typedef {{ + * adunitid: number, + * adid:number, + * type: string, + * htmlz: string, + * framed: number, + * is_fallback: number, + * ts: string, + * cpipc: number, + * pub_rev: string, + * tbd: ?string, + * adv_id: string, + * deal_id: string, + * auct_win_is_deal: number, + * brand_id: string, + * currency: string, + * idx: string, + * creative: Array<OxArjCreative> + * }} + */ + let OxArjAdUnit; + /** + * @typedef {{ + * id: string, + * width: string, + * height: string, + * target: string, + * mime: string, + * media: string, + * tracking: OxArjTracking + * }} + */ + let OxArjCreative; + + // HELPER METHODS + /** + * @type {OxArjCreative} + */ + const DEFAULT_TEST_ARJ_CREATIVE = { + id: '0', + width: 'test-width', + height: 'test-height', + target: 'test-target', + mime: 'test-mime', + media: 'test-media', + tracking: { + impression: 'test-impression', + inview: 'test-inview', + click: 'test-click' + } + }; + + /** + * @type {OxArjAdUnit} + */ + const DEFAULT_TEST_ARJ_AD_UNIT = { + adunitid: 0, + type: 'test-type', + html: 'test-html', + framed: 0, + is_fallback: 0, + ts: 'test-ts', + tbd: 'NaN', + deal_id: undefined, + auct_win_is_deal: undefined, + cpipc: 0, + pub_rev: 'test-pub_rev', + adv_id: 'test-adv_id', + brand_id: 'test-brand_id', + currency: 'test-currency', + idx: '0', + creative: [DEFAULT_TEST_ARJ_CREATIVE] + }; + + /** + * @type {OxArjResponse} + */ + const DEFAULT_ARJ_RESPONSE = { + ads: { + version: 0, + count: 1, + pixels: 'http://testpixels.net', + ad: [DEFAULT_TEST_ARJ_AD_UNIT] + } + }; + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); }); - it('should add empty bid responses if no bids returned', () => { - // empty ads in bidresponse - this.response = JSON.stringify({ - 'ads': - { - 'version': 1, - 'count': 1, - 'pixels': 'http://testpixels.net', - 'ad': [] - } - }); + }); - let bidderRequest = { - bidderCode: 'openx', - bids: [ - { - bidId: 'bidId1', - bidder: 'openx', - params: { - delDomain: 'delDomain1', - unit: '1234' - }, - sizes: [[300, 250]], - placementCode: 'test-gpt-div-1234' - } - ] + describe('isBidRequestValid', () => { + describe('when request is for a banner ad', () => { + const bannerBid = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: {banner: {}}, + sizes: [[300, 250], [300, 600]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' }; - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter.callBids(bidderRequest); + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bannerBid)).to.equal(true); + }); + + it('should return false when there is no delivery domain', () => { + let bid = Object.assign({}, bannerBid); + bid.params = {'unit': '12345678'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidPlacementCode1).to.equal('test-gpt-div-1234'); - expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(bidResponse1.bidderCode).to.equal('openx'); + it('should return false when there is no ad unit id ', () => { + let bid = Object.assign({}, bannerBid); + bid.params = {delDomain: 'test-del-domain'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); }); - it('should add bid responses if bids are returned', () => { - let bidderRequest = { - bidderCode: 'openx', - bids: [ - { - bidId: 'bidId1', - bidder: 'openx', - params: { - delDomain: 'delDomain1', - unit: '1234' - }, - sizes: [[300, 250]], - placementCode: 'test-gpt-div-1234' + 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' }; - this.response = JSON.stringify({ - 'ads': - { - 'version': 1, - 'count': 1, - 'pixels': 'http://testpixels.net', - 'ad': [ - { - 'adunitid': 1234, - 'adid': 5678, - 'type': 'html', - 'html': 'test_html', - 'framed': 1, - 'is_fallback': 0, - 'ts': 'ts', - 'cpipc': 1000, - 'pub_rev': '1000', - 'adv_id': 'adv_id', - 'brand_id': '', - 'creative': [ - { - 'width': '300', - 'height': '250', - 'target': '_blank', - 'mime': 'text/html', - 'media': 'test_media', - 'tracking': { - 'impression': 'test_impression', - 'inview': 'test_inview', - 'click': 'test_click' - } - } - ] - }] - } + const videoBidWithMediaType = { + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': 'video', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(true); }); - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter.callBids(bidderRequest); - - let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; - let bid1width = '300'; - let bid1height = '250'; - let cpm = 1; - expect(bidPlacementCode1).to.equal('test-gpt-div-1234'); - expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidResponse1.bidderCode).to.equal('openx'); - expect(bidResponse1.width).to.equal(bid1width); - expect(bidResponse1.height).to.equal(bid1height); - expect(bidResponse1.cpm).to.equal(cpm); + it('should return false when required params are not passed', () => { + let videoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); + videoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + }); + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let videoBidWithMediaType = Object.assign({}, videoBidWithMediaType); + delete videoBidWithMediaType.params; + videoBidWithMediaType.params = {}; + expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); + }); }); }); - describe('test openx ad requests', () => { - let spyAjax; - let spyBtoa; - beforeEach(() => { - spyAjax = sinon.spy(ajax, 'ajax'); - spyBtoa = sinon.spy(window, 'btoa'); - sinon.stub(document.body, 'appendChild'); - }); - afterEach(() => { - spyAjax.restore(); - spyBtoa.restore(); - document.body.appendChild.restore(); + + describe('buildRequests for banner ads', () => { + const bidRequestsWithMediaType = [{ + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'mediaType': 'banner', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + const bidRequestsWithMediaTypes = [{ + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + mediaTypes: { + 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', () => { + const request = spec.buildRequests(bidRequestsWithMediaType); + expect(request[0].url).to.equal('//' + bidRequestsWithMediaType[0].params.delDomain + URLBASE); + expect(request[0].method).to.equal('GET'); }); - it('should not call ajax when inputting with empty params', () => { - adapter.callBids({}); - assert(!spyAjax.called); + it('should send bid request to openx url via GET, with mediaTypes specified with banner type', () => { + const request = spec.buildRequests(bidRequestsWithMediaTypes); + expect(request[0].url).to.equal('//' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASE); + expect(request[0].method).to.equal('GET'); }); - it('should call ajax with the correct bid url', () => { - let params = { - bids: [ - { - sizes: [[300, 250], [300, 600]], - params: { - delDomain: 'testdelDomain', - unit: 1234 - } - } - ] - }; - adapter.callBids(params); - sinon.assert.calledOnce(spyAjax); + describe('when there is a legacy request with no media type', function () { + const deprecatedBidRequestsFormatWithNoMediaType = [{ + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; - let bidUrl = spyAjax.getCall(0).args[0]; - expect(bidUrl).to.include('testdelDomain'); - expect(bidUrl).to.include('1234'); - expect(bidUrl).to.include('300x250,300x600'); + let requestData; + + beforeEach(function () { + requestData = spec.buildRequests(deprecatedBidRequestsFormatWithNoMediaType)[0].data; + }); + + it('should have an ad unit id', () => { + expect(requestData.auid).to.equal('12345678'); + }); + + it('should have ad sizes', function () { + expect(requestData.aus).to.equal('300x250,300x600'); + }); }); it('should send out custom params on bids that have customParams specified', () => { - let params = { - bids: [ - { - sizes: [[300, 250], [300, 600]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - customParams: {'test1': 'testval1+', 'test2': ['testval2/', 'testval3']} - } + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'customParams': {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} } - ] - }; - adapter.callBids(params); - - sinon.assert.calledOnce(spyAjax); - sinon.assert.calledWith(spyBtoa, 'test1=testval1.&test2=testval2_,testval3'); - let bidUrl = spyAjax.getCall(0).args[0]; - expect(bidUrl).to.include('testdelDomain'); - expect(bidUrl).to.include('1234'); - expect(bidUrl).to.include('300x250,300x600'); + } + ); + + const request = spec.buildRequests([bidRequest]); + const dataParams = request[0].data; + + expect(dataParams.tps).to.exist; + expect(dataParams.tps).to.equal(btoa('test1=testval1.&test2=testval2_,testval3')); }); it('should send out custom floors on bids that have customFloors specified', () => { - let params = { - bids: [ - { - sizes: [[300, 250], [300, 600]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - customFloor: 1 + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'customFloor': 1.5 + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const dataParams = request[0].data; + + expect(dataParams.aumfs).to.exist; + expect(dataParams.aumfs).to.equal('1500'); + }); + + it('should send out custom bc parameter, if override is present', () => { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'bc': 'hb_override' + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const dataParams = request[0].data; + + expect(dataParams.bc).to.exist; + expect(dataParams.bc).to.equal('hb_override'); + }); + + it('should not send any consent management properties', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes); + expect(request[0].data.gdpr).to.equal(undefined); + expect(request[0].data.gdpr_consent).to.equal(undefined); + expect(request[0].data.x_gdpr_f).to.equal(undefined); + }); + + describe('when there is a consent management framework', () => { + let bidRequests; + let mockConfig; + let bidderRequest; + const IAB_CONSENT_FRAMEWORK_CODE = 1; + + beforeEach(() => { + bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678-banner', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] } }, - { - sizes: [[320, 50]], - params: { - delDomain: 'testdelDomain', - unit: 1234 + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }, { + 'bidder': 'openx', + 'mediaTypes': { + video: { + playerSize: [640, 480] } }, - { - sizes: [[728, 90]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - customFloor: 1.5 + 'params': { + 'unit': '12345678-video', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }]; + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + describe('when GDPR applies', function () { + beforeEach(function () { + bidderRequest = { + gdprConsent: { + consentString: 'test-gdpr-consent-string', + gdprApplies: true } - } - ] - }; - adapter.callBids(params); - - sinon.assert.calledOnce(spyAjax); - let bidUrl = spyAjax.getCall(0).args[0]; - expect(bidUrl).to.include('testdelDomain'); - expect(bidUrl).to.include('1234'); - expect(bidUrl).to.include('300x250,300x600|320x50|728x90'); - expect(bidUrl).to.include('aumfs=1000%2C0%2C1500'); + }; + + mockConfig = { + consentManagement: { + cmpApi: 'iab', + timeout: 1111, + allowAuctionWithoutConsent: 'cancel' + } + }; + + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + it('should send a signal to specify that GDPR applies to this request', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.gdpr).to.equal(1); + expect(request[1].data.gdpr).to.equal(1); + }); + + it('should send the consent string', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); + }); + + it('should send the consent management framework code', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); + expect(request[1].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); + }); + }); + + describe('when GDPR does not apply', function () { + beforeEach(function () { + bidderRequest = { + gdprConsent: { + consentString: 'test-gdpr-consent-string', + gdprApplies: false + } + }; + + mockConfig = { + consentManagement: { + cmpApi: 'iab', + timeout: 1111, + allowAuctionWithoutConsent: 'cancel' + } + }; + + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + it('should not send a signal to specify that GDPR does not apply to this request', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.gdpr).to.equal(0); + expect(request[1].data.gdpr).to.equal(0); + }); + + it('should send the consent string', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); + }); + + it('should send the consent management framework code', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); + expect(request[1].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); + }); + }); + + describe('when GDPR consent has undefined data', function () { + beforeEach(function () { + bidderRequest = { + gdprConsent: { + consentString: 'test-gdpr-consent-string', + gdprApplies: true + } + }; + + mockConfig = { + consentManagement: { + cmpApi: 'iab', + timeout: 1111, + allowAuctionWithoutConsent: 'cancel' + } + }; + + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + it('should not send a signal to specify whether GDPR applies to this request, when GDPR application is undefined', function () { + delete bidderRequest.gdprConsent.gdprApplies; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data).to.not.have.property('gdpr'); + expect(request[1].data).to.not.have.property('gdpr'); + }); + + it('should not send the consent string, when consent string is undefined', function () { + delete bidderRequest.gdprConsent.consentString; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data).to.not.have.property('gdpr_consent'); + expect(request[1].data).to.not.have.property('gdpr_consent'); + }); + + it('should not send the consent management framework code, when format is undefined', function () { + delete mockConfig.consentManagement.cmpApi; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data).to.not.have.property('x_gdpr_f'); + expect(request[1].data).to.not.have.property('x_gdpr_f'); + }); + }); + }); + }); + + describe('buildRequests for video', () => { + const bidRequestsWithMediaTypes = [{ + 'bidder': 'openx', + 'mediaTypes': { + video: { + playerSize: [640, 480] + } + }, + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }]; + + const bidRequestsWithMediaType = [{ + 'bidder': 'openx', + 'mediaType': 'video', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }]; + + it('should send bid request to openx url via GET, with mediaType as video', () => { + const request = spec.buildRequests(bidRequestsWithMediaType); + expect(request[0].url).to.equal('//' + 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', () => { + const request = spec.buildRequests(bidRequestsWithMediaTypes); + expect(request[0].url).to.equal('//' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASEVIDEO); + expect(request[0].method).to.equal('GET'); + }); + + it('should have the correct parameters', () => { + const request = spec.buildRequests(bidRequestsWithMediaTypes); + const dataParams = request[0].data; + + expect(dataParams.auid).to.equal('12345678'); + expect(dataParams.vht).to.equal(480); + expect(dataParams.vwd).to.equal(640); }); - it('should change bc param if configureable bc is specified', () => { - let params = { - bids: [ - { - sizes: [[300, 250], [300, 600]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - bc: 'hb_pb_test' + describe('when using the video param', function () { + let videoBidRequest; + + beforeEach(function () { + videoBidRequest = { + 'bidder': 'openx', + 'mediaTypes': { + video: { + context: 'instream', + playerSize: [640, 480] } }, - { - sizes: [[320, 50]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - bc: 'hb_pb_test' - } + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' }, - { - sizes: [[728, 90]], - params: { - delDomain: 'testdelDomain', - unit: 1234, - bc: 'hb_pb_test' + 'adUnitCode': 'adunit-code', + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + } + }); + + it('should not allow you to set a url', function () { + videoBidRequest.params.video = { + url: 'test-url' + }; + const request = spec.buildRequests([videoBidRequest]); + + expect(request[0].data.url).to.be.undefined; + }); + + it('should not allow you to override the javascript url', function () { + let myUrl = 'my-url'; + videoBidRequest.params.video = { + ju: myUrl + }; + const request = spec.buildRequests([videoBidRequest]); + + expect(request[0].data.ju).to.not.equal(myUrl); + }); + + describe('when using the openRtb param', function () { + it('should covert the param to a JSON string', function () { + let myOpenRTBObject = {}; + videoBidRequest.params.video = { + openrtb: myOpenRTBObject + }; + const request = spec.buildRequests([videoBidRequest]); + + expect(request[0].data.openrtb).to.equal(JSON.stringify(myOpenRTBObject)); + }); + + it("should use the bidRequest's playerSize when it is available", function () { + const width = 200; + const height = 100; + const myOpenRTBObject = {v: height, w: width}; + videoBidRequest.params.video = { + openrtb: myOpenRTBObject + }; + const request = spec.buildRequests([videoBidRequest]); + const openRtbRequestParams = JSON.parse(request[0].data.openrtb); + + expect(openRtbRequestParams.w).to.not.equal(width); + expect(openRtbRequestParams.v).to.not.equal(height); + }); + + it('should use the the openRTB\'s sizing when the bidRequest\'s playerSize is not available', function () { + const width = 200; + const height = 100; + const myOpenRTBObject = {v: height, w: width}; + videoBidRequest.params.video = { + openrtb: myOpenRTBObject + }; + videoBidRequest.mediaTypes.video.playerSize = undefined; + + const request = spec.buildRequests([videoBidRequest]); + const openRtbRequestParams = JSON.parse(request[0].data.openrtb); + + expect(openRtbRequestParams.w).to.equal(width); + expect(openRtbRequestParams.v).to.equal(height); + }); + }); + }); + }); + + describe('interpretResponse for banner ads', () => { + beforeEach(() => { + sinon.spy(userSync, 'registerSync'); + }); + + afterEach(function () { + userSync.registerSync.restore(); + }); + + describe('when there is a standard response', function () { + const creativeOverride = { + id: 234, + width: '300', + height: '250', + tracking: { + impression: 'http://openx-d.openx.net/v/1.0/ri?ts=ts' + } + }; + + const adUnitOverride = { + ts: 'test-1234567890-ts', + idx: '0', + currency: 'USD', + pub_rev: '10000', + html: '<div>OpenX Ad</div>' + }; + let adUnit; + let bidResponse; + + let bid; + let bidRequest; + let bidRequestConfigs; + + beforeEach(function () { + bidRequestConfigs = [{ + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'mediaType': 'banner', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + + bidRequest = { + method: 'GET', + url: '//openx-d.openx.net/v/1.0/arj', + data: {}, + payload: {'bids': bidRequestConfigs, 'startTime': new Date()} + }; + + adUnit = mockAdUnit(adUnitOverride, creativeOverride); + bidResponse = mockArjResponse(undefined, [adUnit]); + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + }); + + it('should return a price', function () { + expect(bid.cpm).to.equal(parseInt(adUnitOverride.pub_rev, 10) / 1000); + }); + + it('should return a request id', function () { + expect(bid.requestId).to.equal(bidRequest.payload.bids[0].bidId); + }); + + it('should return width and height for the creative', function () { + expect(bid.width).to.equal(creativeOverride.width); + expect(bid.height).to.equal(creativeOverride.height); + }); + + it('should return a creativeId', function () { + expect(bid.creativeId).to.equal(creativeOverride.id); + }); + + it('should return an ad', function () { + expect(bid.ad).to.equal(adUnitOverride.html); + }); + + it('should have a time-to-live of 5 minutes', function () { + expect(bid.ttl).to.equal(300); + }); + + it('should always return net revenue', function () { + expect(bid.netRevenue).to.equal(true); + }); + + it('should return a currency', function () { + expect(bid.currency).to.equal(adUnitOverride.currency); + }); + + it('should return a transaction state', function () { + expect(bid.ts).to.equal(adUnitOverride.ts); + }); + + it('should register a beacon', () => { + 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}`))); + }); + }); + + describe('when there is a deal', function () { + const adUnitOverride = { + deal_id: 'ox-1000' + }; + let adUnit; + let bidResponse; + + let bid; + let bidRequestConfigs; + let bidRequest; + + beforeEach(function () { + bidRequestConfigs = [{ + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'mediaType': 'banner', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + + bidRequest = { + method: 'GET', + url: '//openx-d.openx.net/v/1.0/arj', + data: {}, + payload: {'bids': bidRequestConfigs, 'startTime': new Date()} + }; + adUnit = mockAdUnit(adUnitOverride); + bidResponse = mockArjResponse(null, [adUnit]); + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + mockArjResponse(); + }); + + it('should return a deal id', function () { + expect(bid.dealId).to.equal(adUnitOverride.deal_id); + }); + }); + + describe('when there is no bids in the response', function () { + let bidRequest; + let bidRequestConfigs; + + beforeEach(function () { + bidRequestConfigs = [{ + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'mediaType': 'banner', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + + bidRequest = { + method: 'GET', + url: '//openx-d.openx.net/v/1.0/arj', + data: {}, + payload: {'bids': bidRequestConfigs, 'startTime': new Date()} + }; + }); + + it('handles nobid responses', () => { + const bidResponse = { + 'ads': + { + 'version': 1, + 'count': 1, + 'pixels': 'http://testpixels.net', + 'ad': [] } + }; + + const result = spec.interpretResponse({body: bidResponse}, bidRequest); + expect(result.length).to.equal(0); + }); + }); + + describe('when adunits return out of order', function () { + const bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[100, 111]] + } + }, + bidId: 'test-bid-request-id-1', + bidderRequestId: 'test-request-1', + auctionId: 'test-auction-id-1' + }, { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[200, 222]] } - ] + }, + bidId: 'test-bid-request-id-2', + bidderRequestId: 'test-request-1', + auctionId: 'test-auction-id-1' + }, { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 333]] + } + }, + 'bidId': 'test-bid-request-id-3', + 'bidderRequestId': 'test-request-1', + 'auctionId': 'test-auction-id-1' + }]; + const bidRequest = { + method: 'GET', + url: '//openx-d.openx.net/v/1.0/arj', + data: {}, + payload: {'bids': bidRequests, 'startTime': new Date()} }; - adapter.callBids(params); - - sinon.assert.calledOnce(spyAjax); - let bidUrl = spyAjax.getCall(0).args[0]; - expect(bidUrl).to.include('testdelDomain'); - expect(bidUrl).to.include('1234'); - expect(bidUrl).to.include('300x250,300x600|320x50|728x90'); - expect(bidUrl).to.include('bc=hb_pb_test'); + + let outOfOrderAdunits = [ + mockAdUnit({ + idx: '1' + }, { + width: bidRequests[1].mediaTypes.banner.sizes[0][0], + height: bidRequests[1].mediaTypes.banner.sizes[0][1] + }), + mockAdUnit({ + idx: '2' + }, { + width: bidRequests[2].mediaTypes.banner.sizes[0][0], + height: bidRequests[2].mediaTypes.banner.sizes[0][1] + }), + mockAdUnit({ + idx: '0' + }, { + width: bidRequests[0].mediaTypes.banner.sizes[0][0], + height: bidRequests[0].mediaTypes.banner.sizes[0][1] + }) + ]; + + let bidResponse = mockArjResponse(undefined, outOfOrderAdunits); + + it('should return map adunits back to the proper request', function () { + const bids = spec.interpretResponse({body: bidResponse}, bidRequest); + expect(bids[0].requestId).to.equal(bidRequests[1].bidId); + expect(bids[0].width).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][0]); + expect(bids[0].height).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][1]); + expect(bids[1].requestId).to.equal(bidRequests[2].bidId); + expect(bids[1].width).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][0]); + expect(bids[1].height).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][1]); + expect(bids[2].requestId).to.equal(bidRequests[0].bidId); + expect(bids[2].width).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][0]); + expect(bids[2].height).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][1]); + }); }); }); + + describe('interpretResponse for video ads', () => { + beforeEach(() => { + sinon.spy(userSync, 'registerSync'); + }); + + afterEach(function () { + userSync.registerSync.restore(); + }); + + const bidsWithMediaTypes = [{ + 'bidder': 'openx', + 'mediaTypes': {video: {}}, + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }]; + const bidsWithMediaType = [{ + 'bidder': 'openx', + 'mediaType': 'video', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }]; + const bidRequestsWithMediaTypes = { + method: 'GET', + url: '//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', + data: {}, + payload: {'bid': bidsWithMediaType[0], 'startTime': new Date()} + }; + const bidResponse = { + 'pub_rev': '1', + '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' + }; + + it('should return correct bid response with MediaTypes', () => { + const expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'bidderCode': 'openx', + 'cpm': 1, + 'width': '640', + 'height': '480', + 'mediaType': 'video', + 'creativeId': '5678', + 'vastUrl': 'http://testvast.com', + 'ttl': 300, + 'netRevenue': true, + 'currency': 'USD' + } + ]; + + const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); + expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); + }); + + it('should return correct bid response with MediaType', () => { + const expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'bidderCode': 'openx', + 'cpm': 1, + 'width': '640', + 'height': '480', + 'mediaType': 'video', + 'creativeId': '5678', + 'vastUrl': 'http://testvast.com', + 'ttl': 300, + 'netRevenue': true, + 'currency': 'USD' + } + ]; + + const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaType); + expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); + }); + + it('should handle nobid responses for bidRequests with MediaTypes', () => { + const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; + const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); + expect(result.length).to.equal(0); + }); + + it('should handle nobid responses for bidRequests with MediaType', () => { + const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; + const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaType); + expect(result.length).to.equal(0); + }); + + it('should register a beacon', () => { + 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(/ph=test-ph/)); + sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(/ts=test-ts/)); + }); + }); + + describe('user sync', () => { + const syncUrl = 'http://testpixels.net'; + + it('should register the pixel iframe from banner ad response', () => { + 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', () => { + 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', () => { + let syncs = spec.getUserSyncs( + {iframeEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'iframe', url: '//u.openx.net/w/1.0/pd'}]); + }); + }); + + /** + * Makes sure the override object does not introduce + * new fields against the contract + * + * This does a shallow check in order to make key checking simple + * with respect to what a helper handles. For helpers that have + * nested fields, either check your design on maybe breaking it up + * to smaller, manageable pieces + * + * OR just call this on your nth level field if necessary. + * + * @param {Object} override Object with keys that overrides the default + * @param {Object} contract Original object contains the default fields + * @param {string} typeName Name of the type we're checking for error messages + * @throws {AssertionError} + */ + function overrideKeyCheck(override, contract, typeName) { + expect(contract).to.include.all.keys(Object.keys(override)); + } + + /** + * Creates a mock ArjResponse + * @param {OxArjResponse=} response + * @param {Array<OxArjAdUnit>=} adUnits + * @throws {AssertionError} + * @return {OxArjResponse} + */ + function mockArjResponse(response, adUnits = []) { + let mockedArjResponse = utils.deepClone(DEFAULT_ARJ_RESPONSE); + + if (response) { + overrideKeyCheck(response, DEFAULT_ARJ_RESPONSE, 'OxArjResponse'); + overrideKeyCheck(response.ads, DEFAULT_ARJ_RESPONSE.ads, 'OxArjResponse'); + Object.assign(mockedArjResponse, response); + } + + if (adUnits.length) { + mockedArjResponse.ads.count = adUnits.length; + mockedArjResponse.ads.ad = adUnits.map((adUnit, index) => { + overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit'); + return Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit); + }); + } + + return mockedArjResponse; + } + + /** + * Creates a mock ArjAdUnit + * @param {OxArjAdUnit=} adUnit + * @param {OxArjCreative=} creative + * @throws {AssertionError} + * @return {OxArjAdUnit} + */ + function mockAdUnit(adUnit, creative) { + overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit'); + + let mockedAdUnit = Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit); + + if (creative) { + overrideKeyCheck(creative, DEFAULT_TEST_ARJ_CREATIVE); + if (creative.tracking) { + overrideKeyCheck(creative.tracking, DEFAULT_TEST_ARJ_CREATIVE.tracking, 'OxArjCreative'); + } + Object.assign(mockedAdUnit.creative[0], creative); + } + + return mockedAdUnit; + } }); diff --git a/test/spec/modules/optimaticBidAdapter_spec.js b/test/spec/modules/optimaticBidAdapter_spec.js new file mode 100644 index 00000000000..d701d981f37 --- /dev/null +++ b/test/spec/modules/optimaticBidAdapter_spec.js @@ -0,0 +1,178 @@ +import { expect } from 'chai'; +import { spec, ENDPOINT } from 'modules/optimaticBidAdapter'; +import * as utils from 'src/utils'; + +describe('OptimaticBidAdapter', () => { + let bidRequest; + + beforeEach(() => { + bidRequest = { + bidder: 'optimatic', + params: { + placement: '2chy7Gc2eSQL', + bidfloor: 5.00 + }, + adUnitCode: 'adunit-code', + sizes: [ 640, 480 ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }; + }); + + describe('spec.isBidRequestValid', () => { + it('should return true when the required params are passed', () => { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false when the "bidfloor" param is missing', () => { + bidRequest.params = { + placement: '2chy7Gc2eSQL' + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when the "placement" param is missing', () => { + bidRequest.params = { + bidfloor: 5.00 + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when no bid params are passed', () => { + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when a bid request is not passed', () => { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).to.equal(false); + }); + }); + + describe('spec.buildRequests', () => { + it('should create a POST request for every bid', () => { + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(ENDPOINT + bidRequest.params.placement); + }); + + it('should attach the bid request object', () => { + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].bidRequest).to.equal(bidRequest); + }); + + it('should attach request data', () => { + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + const [ width, height ] = bidRequest.sizes; + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); + }); + + it('must parse bid size from a nested array', () => { + const width = 640; + const height = 480; + bidRequest.sizes = [[ width, height ]]; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + }); + + it('must parse bid size from a string', () => { + const width = 640; + const height = 480; + bidRequest.sizes = `${width}x${height}`; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + }); + + it('must handle an empty bid size', () => { + bidRequest.sizes = []; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video.w).to.equal(undefined); + expect(data.imp[0].video.h).to.equal(undefined); + }); + }); + + describe('spec.interpretResponse', () => { + it('should return no bids if the response is not valid', () => { + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "nurl" and "adm" are missing', () => { + const serverResponse = {seatbid: [{bid: [{price: 5.01}]}]}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "price" is missing', () => { + const serverResponse = {seatbid: [{bid: [{adm: '<VAST></VAST>'}]}]}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid bid response with just "adm"', () => { + const serverResponse = {seatbid: [{bid: [{id: 1, price: 5.01, adm: '<VAST></VAST>'}]}], 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, + vastXml: serverResponse.seatbid[0].bid[0].adm, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true + }; + expect(bidResponse).to.deep.equal(o); + }); + + it('should return a valid bid response with just "nurl"', () => { + const serverResponse = {seatbid: [{bid: [{id: 1, price: 5.01, nurl: 'https://mg-bid-win.optimatic.com/win/134eb262-948a-463e-ad93-bc8b622d399c?wp=${AUCTION_PRICE}'}]}], 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, + vastUrl: serverResponse.seatbid[0].bid[0].nurl, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true + }; + expect(bidResponse).to.deep.equal(o); + }); + + it('should return a valid bid response with "nurl" when both nurl and adm exist', () => { + const serverResponse = {seatbid: [{bid: [{id: 1, price: 5.01, adm: '<VAST></VAST>', nurl: 'https://mg-bid-win.optimatic.com/win/134eb262-948a-463e-ad93-bc8b622d399c?wp=${AUCTION_PRICE}'}]}], 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, + vastUrl: serverResponse.seatbid[0].bid[0].nurl, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true + }; + expect(bidResponse).to.deep.equal(o); + }); + }); +}); diff --git a/test/spec/modules/optimeraBidAdapter_spec.js b/test/spec/modules/optimeraBidAdapter_spec.js new file mode 100644 index 00000000000..413a52d2d7f --- /dev/null +++ b/test/spec/modules/optimeraBidAdapter_spec.js @@ -0,0 +1,76 @@ +import { expect } from 'chai'; +import { spec } from 'modules/optimeraBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('OptimeraAdapter', () => { + 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', () => { + let bid = { + 'bidder': 'optimera', + 'params': { + 'clientID': '0' + }, + 'adUnitCode': 'div-0', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }) + + describe('buildRequests', () => { + let bid = [ + { + 'adUnitCode': 'div-0', + 'auctionId': '1ab30503e03994', + 'bidId': '313e0afede8cdb', + 'bidder': 'optimera', + 'bidderRequestId': '202be1ce3f6194', + 'params': { + 'clientID': '0' + } + } + ]; + it('buildRequests fires', () => { + let request = spec.buildRequests(bid); + expect(request).to.exist; + expect(request.method).to.equal('GET'); + expect(request.payload).to.exist; + expect(request.data.t).to.exist; + }); + }) + + describe('interpretResponse', () => { + let serverResponse = {}; + serverResponse.body = JSON.parse('{"div-0":["RB_K","728x90K"], "timestamp":["RB_K","1507565666"]}'); + var bidRequest = { + 'method': 'get', + 'payload': [ + { + 'bidder': 'optimera', + 'params': { + 'clientID': '0' + }, + 'adUnitCode': 'div-0', + 'bidId': '307440db8538ab' + } + ] + } + it('interpresResponse fires', () => { + let bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses[0].dealId[0]).to.equal('RB_K'); + expect(bidResponses[0].dealId[1]).to.equal('728x90K'); + }); + }); +}); diff --git a/test/spec/modules/orbitsoftBidAdapter_spec.js b/test/spec/modules/orbitsoftBidAdapter_spec.js index 4b24787f56b..50145a1e72e 100644 --- a/test/spec/modules/orbitsoftBidAdapter_spec.js +++ b/test/spec/modules/orbitsoftBidAdapter_spec.js @@ -1,238 +1,39 @@ -describe('Orbitsoft Adapter tests', function () { - const expect = require('chai').expect; - const assert = require('chai').assert; - const OrbitsoftAdapter = require('modules/orbitsoftBidAdapter'); - const bidmanager = require('src/bidmanager'); - const adloader = require('src/adloader'); - const CONSTANTS = require('src/constants.json'); - - const contentCallEndPoint = 'http://orbitsoft.com/ads/show/content?'; - const jptCallEndPoint = 'http://orbitsoft.com/ads/show/hb?'; - - before(() => sinon.stub(document.body, 'appendChild')); - after(() => document.body.appendChild.restore()); - - describe('test orbitsoft callback response', function () { - it('should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.handleOASCB).to.exist.and.to.be.a('function'); - }); - - it('should add empty bid responses if no bids returned', function () { - let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - let adapter = new OrbitsoftAdapter(); - - let bidderRequest = { - bidderCode: 'orbitsoft', - bids: [ - { - bidId: 'bidIdOrbitsoft1', +import {expect} from 'chai'; +import {spec} from 'modules/orbitsoftBidAdapter'; + +const ENDPOINT_URL = 'https://orbitsoft.com/php/ads/hb.phps'; +describe('Orbitsoft adapter', () => { + describe('implementation', () => { + describe('for requests', () => { + it('should accept valid bid', () => { + let validBid = { bidder: 'orbitsoft', params: { - placementId: '16', - requestUrl: jptCallEndPoint - }, - sizes: [[300, 250]], - placementCode: 'test-div-12345' - } - ] - }; - - // Empty bid response - let response = { - callback_uid: 'bidIdOrbitsoft1', - cpm: 0 - }; - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - $$PREBID_GLOBAL$$.handleOASCB(response); - - let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; - expect(bidPlacementCode1).to.equal('test-div-12345'); - expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(bidResponse1.bidderCode).to.equal('orbitsoft'); - stubAddBidResponse.restore(); - }); - - it('should add empty bid responses if no bidId returned', function () { - let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - let adapter = new OrbitsoftAdapter(); - - let bidderRequest = { - bidderCode: 'orbitsoft', - bids: [ - { - bidId: 'bidIdOrbitsoft1', - bidder: 'orbitsoft', - params: { - placementId: '16', - requestUrl: jptCallEndPoint - }, - sizes: [[300, 250]], - placementCode: 'test-div-12345' - } - ] - }; - - // Empty bid response - let response = { - cpm: 0 - }; - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - $$PREBID_GLOBAL$$.handleOASCB(response); - - expect(stubAddBidResponse.getCall(0)).to.equal(null); - stubAddBidResponse.restore(); - }); - }); - - it('should add bid responses if bids are returned', function () { - let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - let adapter = new OrbitsoftAdapter(); - - let bidderRequest = { - bidderCode: 'orbitsoft', - bids: [ - { - bidId: 'bidIdOrbitsoft1', - bidder: 'orbitsoft', - params: { - placementId: '16', - requestUrl: jptCallEndPoint - }, - sizes: [[300, 250]], - placementCode: 'test-div-12345' - } - ] - }; - - // Bid response - let response = { - callback_uid: 'bidIdOrbitsoft1', - content_url: contentCallEndPoint + 'id=1_201707031440_56069e8e70318303e5869fad86722cb0', - cpm: 0.03, - width: 300, - height: 250 - }; - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - $$PREBID_GLOBAL$$.handleOASCB(response); - - let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; - let bid1width = 300; - let bid1height = 250; - let cpm = 0.03; - let content_url = contentCallEndPoint + 'id=1_201707031440_56069e8e70318303e5869fad86722cb0'; - expect(bidPlacementCode1).to.equal('test-div-12345'); - expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidResponse1.bidderCode).to.equal('orbitsoft'); - expect(bidResponse1.width).to.equal(bid1width); - expect(bidResponse1.height).to.equal(bid1height); - expect(bidResponse1.cpm).to.equal(cpm); - expect(bidResponse1.adUrl).to.equal(content_url); - stubAddBidResponse.restore(); - }); - - it('should call loadscript with the correct params', function () { - let adapter = new OrbitsoftAdapter(); - let spyLoadScript = sinon.spy(adloader, 'loadScript'); - let params = { - bids: [ - { - sizes: [[300, 250], [300, 600]], - params: { - placementId: '16', - requestUrl: jptCallEndPoint - } - } - ] - }; - adapter.callBids(params); - - sinon.assert.calledOnce(spyLoadScript); - - let bidUrl = spyLoadScript.getCall(0).args[0]; - expect(bidUrl).to.include(jptCallEndPoint); - expect(bidUrl).to.include('scid=16'); - expect(bidUrl).to.include('size=300x250'); - expect(bidUrl).to.include('loc'); - spyLoadScript.restore(); - }); - - describe('test orbitsoft callback with params', function () { - it('should not call loadscript when inputting with empty params', function () { - let adapter = new OrbitsoftAdapter(); - let spyLoadScript = sinon.spy(adloader, 'loadScript'); - adapter.callBids({}); - assert(!spyLoadScript.called); - spyLoadScript.restore(); - }); - - it('should not call loadscript when inputting without requestUrl param ', function () { - let adapter = new OrbitsoftAdapter(); - let spyLoadScript = sinon.spy(adloader, 'loadScript'); - let params = { - bids: [ - { - params: { - placementId: '16' + placementId: '123', + requestUrl: ENDPOINT_URL } - } - ] - }; - adapter.callBids(params); - assert(!spyLoadScript.called); - spyLoadScript.restore(); - }); - - it('should not call loadscript when inputting with empty params by string ', function () { - let adapter = new OrbitsoftAdapter(); - let spyLoadScript = sinon.spy(adloader, 'loadScript'); - adapter.callBids(''); - assert(!spyLoadScript.called); - spyLoadScript.restore(); - }); + }, + isValid = spec.isBidRequestValid(validBid); - it('should call loadscript without size in params', function () { - let adapter = new OrbitsoftAdapter(); - let spyLoadScript = sinon.spy(adloader, 'loadScript'); - let params = { - bids: [ - { - params: { - placementId: '16', - requestUrl: jptCallEndPoint - } - } - ] - }; - adapter.callBids(params); + expect(isValid).to.equal(true); + }); - sinon.assert.calledOnce(spyLoadScript); + it('should reject invalid bid', () => { + let invalidBid = { + bidder: 'orbitsoft' + }, + isValid = spec.isBidRequestValid(invalidBid); - let bidUrl = spyLoadScript.getCall(0).args[0]; - expect(bidUrl).to.include(jptCallEndPoint); - expect(bidUrl).to.include('scid=16'); - expect(bidUrl).to.not.include('size='); - expect(bidUrl).to.include('loc'); - spyLoadScript.restore(); + expect(isValid).to.equal(false); + }); }); - - it('should add style params to adUrl if bids are returned', function () { - let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - let adapter = new OrbitsoftAdapter(); - - let bidderRequest = { - bidderCode: 'orbitsoft', - bids: [ - { - bidId: 'bidIdOrbitsoft2', + describe('for requests', () => { + it('should accept valid bid with styles', () => { + let validBid = { bidder: 'orbitsoft', params: { - placementId: '16', - requestUrl: jptCallEndPoint, + placementId: '123', + requestUrl: ENDPOINT_URL, style: { title: { family: 'Tahoma', @@ -261,94 +62,187 @@ describe('Orbitsoft Adapter tests', function () { link: '5B99FE' } } - }, - sizes: [[300, 250]], - placementCode: 'test-div-12345' - } - ] - }; + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + + let buildRequest = spec.buildRequests([validBid])[0]; + let requestUrl = buildRequest.url; + let requestUrlParams = buildRequest.data; + expect(requestUrl).to.equal(ENDPOINT_URL); + expect(requestUrlParams).have.property('f1', 'Tahoma'); + expect(requestUrlParams).have.property('fs1', 'medium'); + expect(requestUrlParams).have.property('w1', 'normal'); + expect(requestUrlParams).have.property('s1', 'normal'); + expect(requestUrlParams).have.property('c3', '0053F9'); + expect(requestUrlParams).have.property('f2', 'Tahoma'); + expect(requestUrlParams).have.property('fs2', 'medium'); + expect(requestUrlParams).have.property('w2', 'normal'); + expect(requestUrlParams).have.property('s2', 'normal'); + expect(requestUrlParams).have.property('c4', '0053F9'); + expect(requestUrlParams).have.property('f3', 'Tahoma'); + expect(requestUrlParams).have.property('fs3', 'medium'); + expect(requestUrlParams).have.property('w3', 'normal'); + expect(requestUrlParams).have.property('s3', 'normal'); + expect(requestUrlParams).have.property('c5', '0053F9'); + expect(requestUrlParams).have.property('c2', 'ffffff'); + expect(requestUrlParams).have.property('c1', 'E0E0E0'); + expect(requestUrlParams).have.property('c6', '5B99FE'); + }); + + it('should accept valid bid with custom params', () => { + let validBid = { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL, + customParams: { + cacheBuster: 'bf4d7c1', + clickUrl: 'http://testclickurl.com' + } + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); - // Bid response with content_url - let response = { - callback_uid: 'bidIdOrbitsoft2', - content_url: contentCallEndPoint + 'id=1_201707031440_56069e8e70318303e5869fad86722cb0', - cpm: 0.03, - width: 300, - height: 250 - }; + let buildRequest = spec.buildRequests([validBid])[0]; + let requestUrlCustomParams = buildRequest.data; + expect(requestUrlCustomParams).have.property('c.cacheBuster', 'bf4d7c1'); + expect(requestUrlCustomParams).have.property('c.clickUrl', 'http://testclickurl.com'); + }); - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); + it('should reject invalid bid without requestUrl', () => { + let invalidBid = { + bidder: 'orbitsoft', + params: { + placementId: '123' + } + }, + isValid = spec.isBidRequestValid(invalidBid); - $$PREBID_GLOBAL$$.handleOASCB(response); + expect(isValid).to.equal(false); + }); - let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; - let adUrl = bidResponse1.adUrl; - let content_url = contentCallEndPoint + 'id=1_201707031440_56069e8e70318303e5869fad86722cb0'; - expect(adUrl).to.include(content_url); - expect(adUrl).to.include('f1=Tahoma'); - expect(adUrl).to.include('fs1=medium'); - expect(adUrl).to.include('w1=normal'); - expect(adUrl).to.include('s1=normal'); - expect(adUrl).to.include('c3=0053F9'); - expect(adUrl).to.include('f2=Tahoma'); - expect(adUrl).to.include('fs2=medium'); - expect(adUrl).to.include('w2=normal'); - expect(adUrl).to.include('s2=normal'); - expect(adUrl).to.include('c4=0053F9'); - expect(adUrl).to.include('f3=Tahoma'); - expect(adUrl).to.include('fs3=medium'); - expect(adUrl).to.include('w3=normal'); - expect(adUrl).to.include('s3=normal'); - expect(adUrl).to.include('c5=0053F9'); - expect(adUrl).to.include('c2=ffffff'); - expect(adUrl).to.include('c1=E0E0E0'); - expect(adUrl).to.include('c6=5B99FE'); + it('should reject invalid bid without placementId', () => { + let invalidBid = { + bidder: 'orbitsoft', + params: { + requestUrl: ENDPOINT_URL + } + }, + isValid = spec.isBidRequestValid(invalidBid); - stubAddBidResponse.restore(); + expect(isValid).to.equal(false); + }); }); + describe('bid responses', () => { + it('should return complete bid response', () => { + let serverResponse = { + body: { + callback_uid: '265b29b70cc106', + cpm: 0.5, + width: 240, + height: 240, + content_url: 'https://orbitsoft.com/php/ads/hb.html', + } + }; + + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + expect(bids).to.be.lengthOf(1); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].width).to.equal(240); + expect(bids[0].height).to.equal(240); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adUrl).to.have.length.above(1); + expect(bids[0].adUrl).to.have.string('https://orbitsoft.com/php/ads/hb.html'); + }); + + it('should return empty bid response', () => { + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let serverResponse = { + body: { + callback_uid: '265b29b70cc106', + cpm: 0 + } + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); - it('should add custom params to adUrl if bids are returned', function () { - let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - let adapter = new OrbitsoftAdapter(); + expect(bids).to.be.lengthOf(0); + }); - let bidderRequest = { - bidderCode: 'orbitsoft', - bids: [ + it('should return empty bid response on incorrect size', () => { + let bidRequests = [ { - bidId: 'bidIdOrbitsoft3', bidder: 'orbitsoft', params: { - placementId: '16', - requestUrl: jptCallEndPoint, - customParams: { - macro_name: 'macro_value' - } - }, - sizes: [[300, 250]], - placementCode: 'test-div-12345' + placementId: '123', + requestUrl: ENDPOINT_URL + } } - ] - }; + ]; + let serverResponse = { + body: { + callback_uid: '265b29b70cc106', + cpm: 1.5, + width: 0, + height: 0 + } + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); - // Bid response with custom params - let response = { - callback_uid: 'bidIdOrbitsoft3', - content_url: contentCallEndPoint + 'id=1_201707031440_56069e8e70318303e5869fad86722cb0', - cpm: 0.03, - width: 300, - height: 250 - }; + it('should return empty bid response with error', () => { + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let serverResponse = {error: 'error'}, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - $$PREBID_GLOBAL$$.handleOASCB(response); + expect(bids).to.be.lengthOf(0); + }); - let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; - let adUrl = bidResponse1.adUrl; - let content_url = contentCallEndPoint + 'id=1_201707031440_56069e8e70318303e5869fad86722cb0'; - expect(adUrl).to.include(content_url); - expect(adUrl).to.include('c.macro_name=macro_value'); + it('should return empty bid response on empty body', () => { + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let serverResponse = {}, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); - stubAddBidResponse.restore(); + expect(bids).to.be.lengthOf(0); + }); }); }); }); diff --git a/test/spec/modules/peak226BidAdapter_spec.js b/test/spec/modules/peak226BidAdapter_spec.js new file mode 100644 index 00000000000..f85e46c4289 --- /dev/null +++ b/test/spec/modules/peak226BidAdapter_spec.js @@ -0,0 +1,114 @@ +import { expect } from 'chai'; +import { spec } from 'modules/peak226BidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const URL = 'a.ad216.com/header_bid'; + +describe('PeakAdapter', () => { + const adapter = newBidder(spec); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const bid = { + params: { + uid: 123 + } + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + const bid = { + params: {} + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + // xdescribe('buildRequests', () => { + // const bidRequests = [ + // { + // params: { + // uid: '1234' + // } + // } + // ]; + + // it('sends bid request to URL via GET', () => { + // const request = spec.buildRequests(bidRequests); + + // expect(request.url).to.equal(`${URL}?uids=1234`); + // expect(request.method).to.equal('GET'); + // }); + // }); + + describe('interpretResponse', () => { + it('should handle empty response', () => { + let bids = spec.interpretResponse( + {}, + { + bidsMap: {} + } + ); + + expect(bids).to.be.lengthOf(0); + }); + + it('should handle no seatbid returned', () => { + let response = {}; + + let bids = spec.interpretResponse( + { body: response }, + { + bidsMap: {} + } + ); + + expect(bids).to.be.lengthOf(0); + }); + + it('should handle empty seatbid returned', () => { + let response = { seatbid: [] }; + + let bids = spec.interpretResponse( + { body: response }, + { + bidsMap: {} + } + ); + + expect(bids).to.be.lengthOf(0); + }); + + it('should handle seatbid returned bids', () => { + const bidsMap = { 1: [{ bidId: 11 }] }; + const bid = { + price: 0.2, + auid: 1, + h: 250, + w: 300, + adm: 'content' + }; + const response = { + seatbid: [ + { + seat: 'foo', + bid: [bid] + } + ] + }; + + let bids = spec.interpretResponse({ body: response }, { bidsMap }); + + expect(bids).to.be.lengthOf(1); + + expect(bids[0].cpm).to.equal(bid.price); + expect(bids[0].width).to.equal(bid.w); + expect(bids[0].height).to.equal(bid.h); + expect(bids[0].ad).to.equal(bid.adm); + expect(bids[0].bidderCode).to.equal(spec.code); + }); + }); +}); diff --git a/test/spec/modules/piximediaBidAdapter_spec.js b/test/spec/modules/piximediaBidAdapter_spec.js deleted file mode 100644 index 14834c81714..00000000000 --- a/test/spec/modules/piximediaBidAdapter_spec.js +++ /dev/null @@ -1,416 +0,0 @@ -describe('Piximedia adapter tests', function () { - var expect = require('chai').expect; - var urlParse = require('url-parse'); - - // var querystringify = require('querystringify'); - - var Adapter = require('modules/piximediaBidAdapter'); - var adLoader = require('src/adloader'); - var bidmanager = require('src/bidmanager'); - var utils = require('src/utils'); - var CONSTANTS = require('src/constants.json'); - - let stubLoadScript; - - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - - afterEach(function () { - stubLoadScript.restore(); - }); - - describe('creation of prebid url', function () { - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; - } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; - } - if (typeof ($$PREBID_GLOBAL$$._adsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._adsReceived = []; - } - - it('should call the Piximedia prebid URL once on valid calls', function () { - var params = { - bidderCode: 'piximedia', - bidder: 'piximedia', - bids: [ - { - bidId: '4d3819cffc4d12', - sizes: [[300, 250]], - bidder: 'piximedia', - params: { siteId: 'TEST', placementId: 'TEST', prebidUrl: '//resources.pm/tests/prebid/bids.js' }, - requestId: '59c318fd382219', - placementCode: '/20164912/header-bid-tag-0' - } - ] - }; - - new Adapter().callBids(params); - sinon.assert.calledOnce(stubLoadScript); - }); - - it('should not call the Piximedia prebid URL once on invalid calls', function () { - var params = { - bidderCode: 'piximedia', - bidder: 'piximedia', - bids: [ - { - bidId: '4d3819cffc4d12', - sizes: [[300, 250]], - bidder: 'piximedia', - params: { prebidUrl: '//resources.pm/tests/prebid/bids.js' }, // this is invalid: site and placement ID are missing - requestId: '59c318fd382219', - placementCode: '/20164912/header-bid-tag-0' - } - ] - }; - - new Adapter().callBids(params); - sinon.assert.notCalled(stubLoadScript); - }); - - it('should call the correct Prebid URL when using the default URL', function () { - var params = { - bidderCode: 'piximedia', - bidder: 'piximedia', - bids: [ - { - bidId: '4d3819cffc4d12', - sizes: [[300, 250]], - bidder: 'piximedia', - params: { siteId: 'TEST', placementId: 'TEST' }, - requestId: '59c318fd382219', - placementCode: '/20164912/header-bid-tag-0' - } - ] - }; - - new Adapter().callBids(params); - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledWith(stubLoadScript, bidUrl); - - var parsedBidUrl = urlParse(bidUrl); - - expect(parsedBidUrl.hostname).to.equal('static.adserver.pm'); - expect(parsedBidUrl.query).to.equal(''); - expect(parsedBidUrl.pathname.replace(/cbid=[a-f0-9]+/, 'cbid=210af5668b1e23').replace(/rand=[0-9]+$/, 'rand=42')).to.equal('/prebid/site_id=TEST/placement_id=TEST/jsonp=$$PREBID_GLOBAL$$.handlePiximediaCallback/sizes=300x250/cbid=210af5668b1e23/rand=42'); - }); - - it('should call the correct Prebid URL when using the default URL with a deal and custom data', function () { - var params = { - bidderCode: 'piximedia', - bidder: 'piximedia', - bids: [ - { - bidId: '4d3819cffc4d12', - sizes: [[300, 250]], - bidder: 'piximedia', - params: { siteId: 'TEST', placementId: 'TEST', dealId: 1295, custom: 'bespoke', custom2: function() { return 'bespoke2'; }, custom3: null, custom4: function() {} }, - requestId: '59c318fd382219', - placementCode: '/20164912/header-bid-tag-0' - } - ] - }; - - new Adapter().callBids(params); - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledWith(stubLoadScript, bidUrl); - - var parsedBidUrl = urlParse(bidUrl); - - expect(parsedBidUrl.hostname).to.equal('static.adserver.pm'); - expect(parsedBidUrl.query).to.equal(''); - expect(parsedBidUrl.pathname.replace(/cbid=[a-f0-9]+/, 'cbid=210af5668b1e23').replace(/rand=[0-9]+$/, 'rand=42')).to.equal('/prebid/site_id=TEST/placement_id=TEST/l_id=1295/custom=bespoke/custom2=bespoke2/custom3=/custom4=/jsonp=$$PREBID_GLOBAL$$.handlePiximediaCallback/sizes=300x250/cbid=210af5668b1e23/rand=42'); - }); - - it('should call the correct Prebid URL when using the default URL and overridding sizes', function () { - var params = { - bidderCode: 'piximedia', - bidder: 'piximedia', - bids: [ - { - bidId: '4d3819cffc4d12', - sizes: [[300, 250]], - bidder: 'piximedia', - params: { siteId: 'TEST', placementId: 'TEST', sizes: [[300, 600], [728, 90]] }, - requestId: '59c318fd382219', - placementCode: '/20164912/header-bid-tag-0' - } - ] - }; - - new Adapter().callBids(params); - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledWith(stubLoadScript, bidUrl); - - var parsedBidUrl = urlParse(bidUrl); - - expect(parsedBidUrl.hostname).to.equal('static.adserver.pm'); - expect(parsedBidUrl.query).to.equal(''); - expect(parsedBidUrl.pathname.replace(/cbid=[a-f0-9]+/, 'cbid=210af5668b1e23').replace(/rand=[0-9]+$/, 'rand=42')).to.equal('/prebid/site_id=TEST/placement_id=TEST/jsonp=$$PREBID_GLOBAL$$.handlePiximediaCallback/sizes=300x600%2C728x90/cbid=210af5668b1e23/rand=42'); - }); - - it('should call the correct Prebid URL when supplying a custom URL', function () { - var params = { - bidderCode: 'piximedia', - bidder: 'piximedia', - bids: [ - { - bidId: '4d3819cffc4d12', - sizes: [[300, 250]], - bidder: 'piximedia', - params: { siteId: 'TEST', placementId: 'TEST', prebidUrl: '//resources.pm/tests/prebid/bids.js' }, - requestId: '59c318fd382219', - placementCode: '/20164912/header-bid-tag-0' - } - ] - }; - - new Adapter().callBids(params); - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledWith(stubLoadScript, bidUrl); - - var parsedBidUrl = urlParse(bidUrl); - - expect(parsedBidUrl.hostname).to.equal('resources.pm'); - expect(parsedBidUrl.query).to.equal(''); - expect(parsedBidUrl.pathname.replace(/cbid=[a-f0-9]+/, 'cbid=210af5668b1e23').replace(/rand=[0-9]+$/, 'rand=42')).to.equal('/tests/prebid/bids.js/site_id=TEST/placement_id=TEST/jsonp=$$PREBID_GLOBAL$$.handlePiximediaCallback/sizes=300x250/cbid=210af5668b1e23/rand=42'); - }); - }); - - describe('handling of the callback response', function () { - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; - } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; - } - if (typeof ($$PREBID_GLOBAL$$._adsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._adsReceived = []; - } - - var params = { - bidderCode: 'piximedia', - bidder: 'piximedia', - bids: [ - { - bidId: '4d3819cffc4d12', - sizes: [[300, 250]], - bidder: 'piximedia', - params: { siteId: 'TEST', placementId: 'TEST', prebidUrl: '//resources.pm/tests/prebid/bids.js' }, - requestId: '59c318fd382219', - placementCode: '/20164912/header-bid-tag-0' - } - ] - }; - - it('Piximedia callback function should exist', function () { - expect($$PREBID_GLOBAL$$.handlePiximediaCallback).to.exist.and.to.be.a('function'); - }); - - it('bidmanager.addBidResponse should be called once with correct arguments', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - var stubGetUniqueIdentifierStr = sinon.spy(utils, 'getUniqueIdentifierStr'); - - var response = { - foundbypm: true, - currency: 'EUR', - cpm: 1.23, - dealId: 9948, - width: 300, - height: 250, - html: '<div>ad</div>' - }; - - new Adapter().callBids(params); - - var adUnits = []; - var unit = {}; - unit.bids = [params]; - unit.code = '/20164912/header-bid-tag'; - unit.sizes = [[300, 250], [728, 90]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - $$PREBID_GLOBAL$$.adUnits = adUnits; - response.cbid = stubGetUniqueIdentifierStr.returnValues[0]; - - $$PREBID_GLOBAL$$.handlePiximediaCallback(response); - - sinon.assert.calledOnce(stubAddBidResponse); - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('/20164912/header-bid-tag-0'); - expect(bidObject1.cpm).to.equal(1.23); - expect(bidObject1.ad).to.equal('<div>ad</div>'); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.dealId).to.equal(9948); - expect(bidObject1.height).to.equal(250); - expect(bidObject1.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidObject1.bidderCode).to.equal('piximedia'); - - stubAddBidResponse.restore(); - stubGetUniqueIdentifierStr.restore(); - }); - - it('bidmanager.addBidResponse should be called once with correct arguments on partial response', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - var stubGetUniqueIdentifierStr = sinon.spy(utils, 'getUniqueIdentifierStr'); - - // this time, we do not provide dealId - var response = { - foundbypm: true, - cpm: 1.23, - width: 300, - height: 250, - currency: 'EUR', - html: '<div>ad</div>' - }; - - new Adapter().callBids(params); - - var adUnits = []; - var unit = {}; - unit.bids = [params]; - unit.code = '/20164912/header-bid-tag'; - unit.sizes = [[300, 250], [728, 90]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - $$PREBID_GLOBAL$$.adUnits = adUnits; - response.cbid = stubGetUniqueIdentifierStr.returnValues[0]; - - $$PREBID_GLOBAL$$.handlePiximediaCallback(response); - - sinon.assert.calledOnce(stubAddBidResponse); - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('/20164912/header-bid-tag-0'); - expect(bidObject1.cpm).to.equal(1.23); - expect(bidObject1.ad).to.equal('<div>ad</div>'); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.height).to.equal(250); - expect(bidObject1.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidObject1.bidderCode).to.equal('piximedia'); - - stubAddBidResponse.restore(); - stubGetUniqueIdentifierStr.restore(); - }); - - it('bidmanager.addBidResponse should be called once without any ads', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - var stubGetUniqueIdentifierStr = sinon.spy(utils, 'getUniqueIdentifierStr'); - - var response = { - foundbypm: false - }; - - new Adapter().callBids(params); - - var adUnits = []; - var unit = {}; - unit.bids = [params]; - unit.code = '/20164912/header-bid-tag'; - unit.sizes = [[300, 250], [728, 90]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - $$PREBID_GLOBAL$$.adUnits = adUnits; - response.cbid = stubGetUniqueIdentifierStr.returnValues[0]; - - $$PREBID_GLOBAL$$.handlePiximediaCallback(response); - - sinon.assert.calledOnce(stubAddBidResponse); - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('/20164912/header-bid-tag-0'); - expect(bidObject1.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(bidObject1.bidderCode).to.equal('piximedia'); - - stubAddBidResponse.restore(); - stubGetUniqueIdentifierStr.restore(); - }); - - it('bidmanager.addBidResponse should not be called on bogus cbid', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - var stubGetUniqueIdentifierStr = sinon.spy(utils, 'getUniqueIdentifierStr'); - - var response = { - foundbypm: false - }; - - new Adapter().callBids(params); - - var adUnits = []; - var unit = {}; - unit.bids = [params]; - unit.code = '/20164912/header-bid-tag'; - unit.sizes = [[300, 250], [728, 90]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - $$PREBID_GLOBAL$$.adUnits = adUnits; - response.cbid = stubGetUniqueIdentifierStr.returnValues[0] + '_BOGUS'; - - $$PREBID_GLOBAL$$.handlePiximediaCallback(response); - - sinon.assert.notCalled(stubAddBidResponse); - - stubAddBidResponse.restore(); - stubGetUniqueIdentifierStr.restore(); - }); - - it('bidmanager.addBidResponse should not be called on bogus response', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var response = null; // this is bogus: we expect an object - - new Adapter().callBids(params); - - var adUnits = []; - var unit = {}; - unit.bids = [params]; - unit.code = '/20164912/header-bid-tag'; - unit.sizes = [[300, 250], [728, 90]]; - adUnits.push(unit); - - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$.handlePiximediaCallback(response); - - sinon.assert.notCalled(stubAddBidResponse); - - stubAddBidResponse.restore(); - }); - }); -}); diff --git a/test/spec/modules/platformioBidAdapter_spec.js b/test/spec/modules/platformioBidAdapter_spec.js index 01a6176c58d..fc8ba5bf45e 100644 --- a/test/spec/modules/platformioBidAdapter_spec.js +++ b/test/spec/modules/platformioBidAdapter_spec.js @@ -1,156 +1,316 @@ -describe('platformio adapter tests', function () { - var expect = require('chai').expect; - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - var adapter = require('modules/platformioBidAdapter'); - var adLoader = require('src/adloader'); - var bidmanager = require('src/bidmanager'); +import {expect} from 'chai'; +import {spec} from 'modules/platformioBidAdapter'; +import {getTopWindowLocation} from 'src/utils'; +import {newBidder} from 'src/adapters/bidderFactory'; - var stubLoadScript; - - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - - afterEach(function () { - stubLoadScript.restore(); - }); - - describe('creation of bid url', function () { - if (typeof ($$PREBID_GLOBAL$$._bidsReceived) === 'undefined') { - $$PREBID_GLOBAL$$._bidsReceived = []; +describe('Platform.io Adapter Tests', () => { + const slotConfigs = [{ + placementCode: '/DfpAccount1/slot1', + bidId: 'bid12345', + mediaType: 'banner', + params: { + pubId: '29521', + siteId: '26047', + placementId: '123', + size: '300x250', + bidFloor: '0.001', + ifa: 'IFA', + latitude: '40.712775', + longitude: '-74.005973' } - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = []; + }, { + placementCode: '/DfpAccount2/slot2', + bidId: 'bid23456', + mediaType: 'banner', + params: { + pubId: '29521', + siteId: '26047', + placementId: '1234', + size: '728x90', + bidFloor: '0.000001', } + }]; + const nativeSlotConfig = [{ + placementCode: '/DfpAccount1/slot3', + bidId: 'bid12345', + mediaType: 'native', + nativeParams: { + title: { required: true, len: 200 }, + body: {}, + image: { wmin: 100 }, + sponsoredBy: { }, + icon: { } + }, + params: { + pubId: '29521', + placementId: '123', + siteId: '26047' + } + }]; + const videoSlotConfig = [{ + placementCode: '/DfpAccount1/slot4', + bidId: 'bid12345678', + mediaType: 'video', + video: { + skippable: true + }, + params: { + pubId: '29521', + placementId: '1234567', + siteId: '26047', + size: '640x480' + } + }]; + const appSlotConfig = [{ + placementCode: '/DfpAccount1/slot5', + bidId: 'bid12345', + params: { + pubId: '29521', + placementId: '1234', + app: { + id: '1111', + name: 'app name', + bundle: 'io.platform.apps', + storeUrl: 'http://platform.io/apps', + domain: 'platform.io' + } + } + }]; - it('bid request for single placement', function () { - var params = { - bids: [{ - placementCode: '/19968336/header-bid-tag-0', - sizes: [[300, 250]], - bidId: 'bid1111', - bidder: 'platformio', - params: { pubId: '37054', siteId: '123' } - }] - }; - - adapter().callBids(params); - - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledOnce(stubLoadScript); - - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - expect(parsedBidUrlQueryString).to.have.property('pub_id').and.to.equal('37054'); - expect(parsedBidUrlQueryString).to.have.property('site_id').and.to.equal('123'); - expect(parsedBidUrlQueryString).to.have.property('width').and.to.equal('300'); - expect(parsedBidUrlQueryString).to.have.property('height').and.to.equal('250'); - }); + it('Verify build request', () => { + const request = spec.buildRequests(slotConfigs); + expect(request.url).to.equal('//piohbdisp.hb.adx1.com/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(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('29521'); + expect(ortbRequest.site.ref).to.equal(window.top.document.referrer); + expect(ortbRequest.site.page).to.equal(getTopWindowLocation().href); + expect(ortbRequest.imp).to.have.lengthOf(2); + // device object + expect(ortbRequest.device).to.not.equal(null); + expect(ortbRequest.device.ua).to.equal(navigator.userAgent); + expect(ortbRequest.device.ifa).to.equal('IFA'); + expect(ortbRequest.device.geo.lat).to.equal('40.712775'); + expect(ortbRequest.device.geo.lon).to.equal('-74.005973'); + // slot 1 + expect(ortbRequest.imp[0].tagid).to.equal('123'); + expect(ortbRequest.imp[0].banner).to.not.equal(null); + expect(ortbRequest.imp[0].banner.w).to.equal(300); + expect(ortbRequest.imp[0].banner.h).to.equal(250); + expect(ortbRequest.imp[0].bidfloor).to.equal('0.001'); + // slot 2 + expect(ortbRequest.imp[1].tagid).to.equal('1234'); + expect(ortbRequest.imp[1].banner).to.not.equal(null); + expect(ortbRequest.imp[1].banner.w).to.equal(728); + expect(ortbRequest.imp[1].banner.h).to.equal(90); + expect(ortbRequest.imp[1].bidfloor).to.equal('0.000001'); }); - describe('handling bid response', function () { - it('should return complete bid response', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bids: [{ - placementCode: '/19968336/header-bid-tag-0', - sizes: [[300, 250]], - bidId: 'bid1111', - bidder: 'platformio', - params: { pubId: '37054', siteId: '123' } + it('Verify parse response', () => { + const request = spec.buildRequests(slotConfigs); + const ortbRequest = JSON.parse(request.data); + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'This is an Ad' }] - }; - - var response = { - cpm: 1, - width: 300, - height: 250, - callback_uid: 'bid1111', - tag: '<script>document.write("campaign banner");<\/script>' - }; + }], + cur: 'USD' + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + expect(bids).to.have.lengthOf(1); + // verify first bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.ad).to.equal('This is an Ad'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.adId).to.equal('bid12345'); + expect(bid.creativeId).to.equal('bid12345'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(360); + }); - adapter().callBids(params); + it('Verify full passback', () => { + const request = spec.buildRequests(slotConfigs); + const bids = spec.interpretResponse({ body: null }, request) + expect(bids).to.have.lengthOf(0); + }); - var adUnits = []; - var unit = {}; - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); + it('Verify Native request', () => { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('//piohbdisp.hb.adx1.com/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + // native impression + expect(ortbRequest.imp[0].tagid).to.equal('123'); + const nativePart = ortbRequest.imp[0]['native']; + expect(nativePart).to.not.equal(null); + expect(nativePart.ver).to.equal('1.1'); + expect(nativePart.request).to.not.equal(null); + // native request assets + const nativeRequest = JSON.parse(ortbRequest.imp[0]['native'].request); + expect(nativeRequest).to.not.equal(null); + expect(nativeRequest.assets).to.have.lengthOf(5); + expect(nativeRequest.assets[0].id).to.equal(1); + expect(nativeRequest.assets[1].id).to.equal(2); + expect(nativeRequest.assets[2].id).to.equal(3); + expect(nativeRequest.assets[3].id).to.equal(4); + expect(nativeRequest.assets[4].id).to.equal(5); + expect(nativeRequest.assets[0].required).to.equal(1); + expect(nativeRequest.assets[0].title).to.not.equal(null); + expect(nativeRequest.assets[0].title.len).to.equal(200); + expect(nativeRequest.assets[1].title).to.be.undefined; + expect(nativeRequest.assets[1].data).to.not.equal(null); + expect(nativeRequest.assets[1].data.type).to.equal(2); + expect(nativeRequest.assets[1].data.len).to.equal(200); + expect(nativeRequest.assets[2].required).to.equal(0); + expect(nativeRequest.assets[3].img).to.not.equal(null); + expect(nativeRequest.assets[3].img.wmin).to.equal(50); + expect(nativeRequest.assets[3].img.hmin).to.equal(50); + expect(nativeRequest.assets[3].img.type).to.equal(1); + expect(nativeRequest.assets[4].img).to.not.equal(null); + expect(nativeRequest.assets[4].img.wmin).to.equal(100); + expect(nativeRequest.assets[4].img.hmin).to.equal(150); + expect(nativeRequest.assets[4].img.type).to.equal(3); + }); - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); + it('Verify Native response', () => { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('//piohbdisp.hb.adx1.com/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + const nativeResponse = { + 'native': { + assets: [ + { 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 } } + ], + link: { url: 'http://brand.com/' } } - - $$PREBID_GLOBAL$$.adUnits = adUnits; - - $$PREBID_GLOBAL$$._doPlatformIOCallback(response); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('/19968336/header-bid-tag-0'); - expect(bidObject1.bidderCode).to.equal('platformio'); - expect(bidObject1.cpm).to.equal(1); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.height).to.equal(250); - expect(bidObject1.ad).to.have.length.above(1); - - stubAddBidResponse.restore(); - }); - - it('should return no bid response', function() { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var params = { - bids: [{ - placementCode: '/19968336/header-bid-tag-0', - sizes: [[300, 250]], - bidId: 'bid1111', - bidder: 'platformio', - params: { pubId: '37054', siteId: '123' } + }; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + nurl: 'http://rtb.adx1.com/log', + adm: JSON.stringify(nativeResponse) }] - }; - - var response = { - cpm: 0, - width: 300, - height: 250, - callback_uid: 'bid1111', - tag: '<script>document.write("default banner");<\/script>' - }; - - adapter().callBids(params); - - var adUnits = []; - var unit = {}; - unit.bids = params.bids; - unit.code = '/123456/header-bid-tag-1'; - unit.sizes = [[300, 250]]; - adUnits.push(unit); + }], + cur: 'USD', + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + // verify bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.adId).to.equal('bid12345'); + expect(bid.ad).to.be.undefined; + expect(bid.mediaType).to.equal('native'); + const nativeBid = bid['native']; + expect(nativeBid).to.not.equal(null); + expect(nativeBid.title).to.equal('Ad Title'); + expect(nativeBid.sponsoredBy).to.equal('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.image.width).to.equal(300); + expect(nativeBid.image.height).to.equal(300); + expect(nativeBid.icon.width).to.equal(100); + expect(nativeBid.icon.height).to.equal(100); + expect(nativeBid.clickUrl).to.equal(encodeURIComponent('http://brand.com/')); + expect(nativeBid.impressionTrackers).to.have.lengthOf(1); + expect(nativeBid.impressionTrackers[0]).to.equal('http://rtb.adx1.com/log'); + }); - if (typeof ($$PREBID_GLOBAL$$._bidsRequested) === 'undefined') { - $$PREBID_GLOBAL$$._bidsRequested = [params]; - } else { - $$PREBID_GLOBAL$$._bidsRequested.push(params); - } + it('Verify Video request', () => { + const request = spec.buildRequests(videoSlotConfig); + expect(request.url).to.equal('//piohbdisp.hb.adx1.com/'); + expect(request.method).to.equal('POST'); + const videoRequest = JSON.parse(request.data); + // site object + expect(videoRequest.site).to.not.equal(null); + expect(videoRequest.site.publisher.id).to.equal('29521'); + expect(videoRequest.site.ref).to.equal(window.top.document.referrer); + expect(videoRequest.site.page).to.equal(getTopWindowLocation().href); + // device object + expect(videoRequest.device).to.not.equal(null); + expect(videoRequest.device.ua).to.equal(navigator.userAgent); + // slot 1 + expect(videoRequest.imp[0].tagid).to.equal('1234567'); + expect(videoRequest.imp[0].video).to.not.equal(null); + expect(videoRequest.imp[0].video.w).to.equal(640); + expect(videoRequest.imp[0].video.h).to.equal(480); + expect(videoRequest.imp[0].banner).to.equal(null); + expect(videoRequest.imp[0].native).to.equal(null); + }); - $$PREBID_GLOBAL$$.adUnits = adUnits; + it('Verify parse video response', () => { + const request = spec.buildRequests(videoSlotConfig); + const videoRequest = JSON.parse(request.data); + const videoResponse = { + seatbid: [{ + bid: [{ + impid: videoRequest.imp[0].id, + price: 1.90, + adm: 'http://vid.example.com/9876', + crid: '510511_754567308' + }] + }], + cur: 'USD' + }; + const bids = spec.interpretResponse({ body: videoResponse }, request); + expect(bids).to.have.lengthOf(1); + // verify first bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.90); + expect(bid.vastUrl).to.equal('http://vid.example.com/9876'); + expect(bid.crid).to.equal('510511_754567308'); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + expect(bid.adId).to.equal('bid12345678'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(360); + }); - $$PREBID_GLOBAL$$._doPlatformIOCallback(response); + it('Verifies bidder code', () => { + expect(spec.code).to.equal('platformio'); + }); - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; + it('Verifies supported media types', () => { + 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'); + }); - expect(bidPlacementCode1).to.equal('/19968336/header-bid-tag-0'); - expect(bidObject1.bidderCode).to.equal('platformio'); + it('Verifies if bid request valid', () => { + expect(spec.isBidRequestValid(slotConfigs[0])).to.equal(true); + expect(spec.isBidRequestValid(slotConfigs[1])).to.equal(true); + expect(spec.isBidRequestValid(nativeSlotConfig[0])).to.equal(true); + expect(spec.isBidRequestValid(videoSlotConfig[0])).to.equal(true); + }); - stubAddBidResponse.restore(); - }); + it('Verify app requests', () => { + const request = spec.buildRequests(appSlotConfig); + const ortbRequest = JSON.parse(request.data); + expect(ortbRequest.site).to.equal(null); + expect(ortbRequest.app).to.not.be.null; + expect(ortbRequest.app.publisher).to.not.equal(null); + expect(ortbRequest.app.publisher.id).to.equal('29521'); + expect(ortbRequest.app.id).to.equal('1111'); + expect(ortbRequest.app.name).to.equal('app name'); + expect(ortbRequest.app.bundle).to.equal('io.platform.apps'); + expect(ortbRequest.app.storeurl).to.equal('http://platform.io/apps'); + expect(ortbRequest.app.domain).to.equal('platform.io'); }); }); diff --git a/test/spec/modules/polluxBidAdapter_spec.js b/test/spec/modules/polluxBidAdapter_spec.js index 1bcfe28124d..ea550fecd71 100644 --- a/test/spec/modules/polluxBidAdapter_spec.js +++ b/test/spec/modules/polluxBidAdapter_spec.js @@ -1,172 +1,207 @@ -describe('Pollux Bid Adapter tests', function () { - var expect = require('chai').expect; - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - var Adapter = require('modules/polluxBidAdapter'); - var adLoader = require('src/adloader'); - var bidmanager = require('src/bidmanager'); - var utils = require('src/utils'); - - var stubLoadScript; - var stubAddBidResponse; - var polluxAdapter; - - // mock golbal _bidsRequested var - var bidsRequested = []; - utils.getBidRequest = function (id) { - return bidsRequested.map(bidSet => bidSet.bids.find(bid => bid.bidId === id)).find(bid => bid); - }; - - beforeEach(function () { - polluxAdapter = new Adapter(); - bidsRequested = []; - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); +import {expect} from 'chai'; +import {spec} from 'modules/polluxBidAdapter'; +import {utils} from 'src/utils'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('POLLUX Bid Adapter tests', function () { + // ad units setup + const setup_single_bid = [{ + placementCode: 'div-gpt-ad-1460505661587-0', + bidId: '789s6354sfg856', + bidderUrl: '//adn.polluxnetwork.com/prebid/v1', + sizes: [[728, 90], [300, 250]], + params: {zone: '1806,276'} + }]; + const setup_multi_bid = [{ + placementCode: 'div-gpt-ad-1460505661639-0', + bidId: '21fe992ca48d55', + sizes: [[300, 250]], + params: {zone: '1806'} + }, { + placementCode: 'div-gpt-ad-1460505661812-0', + bidId: '23kljh54390534', + sizes: [[728, 90]], + params: {zone: '276'} + }]; + + it('TEST: verify buildRequests no valid bid requests', () => { + let request = spec.buildRequests(false); + expect(request).to.not.equal(null); + expect(request).to.not.have.property('method'); + expect(request).to.not.have.property('url'); + expect(request).to.not.have.property('data'); + request = spec.buildRequests([]); + expect(request).to.not.equal(null); + expect(request).to.not.have.property('method'); + expect(request).to.not.have.property('url'); + expect(request).to.not.have.property('data'); + request = spec.buildRequests({}); + expect(request).to.not.equal(null); + expect(request).to.not.have.property('method'); + expect(request).to.not.have.property('url'); + expect(request).to.not.have.property('data'); + request = spec.buildRequests(null); + expect(request).to.not.equal(null); + expect(request).to.not.have.property('method'); + expect(request).to.not.have.property('url'); + expect(request).to.not.have.property('data'); }); - afterEach(function () { - stubLoadScript.restore(); - stubAddBidResponse.restore(); + it('TEST: verify buildRequests single bid', () => { + const request = spec.buildRequests(setup_single_bid); + expect(request.method).to.equal('POST'); + const requested_bids = JSON.parse(request.data); + // bids request + expect(requested_bids).to.not.equal(null); + expect(requested_bids).to.have.lengthOf(1); + // bid objects + expect(requested_bids[0]).to.not.equal(null); + expect(requested_bids[0]).to.have.property('bidId'); + expect(requested_bids[0]).to.have.property('sizes'); + expect(requested_bids[0]).to.have.property('zones'); + // bid 0 + expect(requested_bids[0].bidId).to.equal('789s6354sfg856'); + expect(requested_bids[0].sizes).to.not.equal(null); + expect(requested_bids[0].sizes).to.have.lengthOf(2); + expect(requested_bids[0].sizes[0][0]).to.equal(728); + expect(requested_bids[0].sizes[0][1]).to.equal(90); + expect(requested_bids[0].sizes[1][0]).to.equal(300); + expect(requested_bids[0].sizes[1][1]).to.equal(250); + expect(requested_bids[0].zones).to.equal('1806,276'); }); - describe('creation of bid url', function () { - it('bid request for single placement', function () { - var params = { - bidderCode: 'pollux', - bids: [{ - placementCode: 'div-gpt-ad-1460505661639-0', - bidId: '21fe992ca48d55', - bidder: 'pollux', - sizes: [[300, 250]], - params: { zone: '1806' } - }] - }; - - polluxAdapter.callBids(params); - - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledOnce(stubLoadScript); - - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - expect(parsedBidUrlQueryString).to.have.property('zone').and.to.equal('1806'); - expect(parsedBidUrlQueryString).to.have.property('domain').and.to.have.length.above(1); - }); + it('TEST: verify buildRequests multi bid', () => { + const request = spec.buildRequests(setup_multi_bid); + expect(request.method).to.equal('POST'); + const requested_bids = JSON.parse(request.data); + // bids request + expect(requested_bids).to.not.equal(null); + expect(requested_bids).to.have.lengthOf(2); + // bid objects + expect(requested_bids[0]).to.not.equal(null); + expect(requested_bids[0]).to.have.property('bidId'); + expect(requested_bids[0]).to.have.property('sizes'); + expect(requested_bids[0]).to.have.property('zones'); + expect(requested_bids[1]).to.not.equal(null); + expect(requested_bids[1]).to.have.property('bidId'); + expect(requested_bids[1]).to.have.property('sizes'); + expect(requested_bids[1]).to.have.property('zones'); + // bid 0 + expect(requested_bids[0].bidId).to.equal('21fe992ca48d55'); + expect(requested_bids[0].sizes).to.not.equal(null); + expect(requested_bids[0].sizes).to.have.lengthOf(1); + expect(requested_bids[0].sizes[0][0]).to.equal(300); + expect(requested_bids[0].sizes[0][1]).to.equal(250); + expect(requested_bids[0].zones).to.equal('1806'); + // bid 1 + expect(requested_bids[1].bidId).to.equal('23kljh54390534'); + expect(requested_bids[1].sizes).to.not.equal(null); + expect(requested_bids[1].sizes).to.have.lengthOf(1); + expect(requested_bids[1].sizes[0][0]).to.equal(728); + expect(requested_bids[1].sizes[0][1]).to.equal(90); + expect(requested_bids[1].zones).to.equal('276'); }); - describe('handling bid response', function () { - it('should return complete bid response adUrl', function() { - var params = { - bidderCode: 'pollux', - bids: [{ - placementCode: 'div-gpt-ad-1460505661639-0', - sizes: [[300, 250]], - bidId: '21fe992ca48d55', - bidder: 'pollux', - params: { zone: '1806' } - }] - }; - - var response = { - cpm: 0.5, - width: 300, - height: 250, - callback_id: '21fe992ca48d55', - ad: 'some.ad.url', - ad_type: 'url', - zone: 1806 - }; - - polluxAdapter.callBids(params); - bidsRequested.push(params); - polluxAdapter.polluxHandler(response); - - sinon.assert.calledOnce(stubAddBidResponse); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('div-gpt-ad-1460505661639-0'); - expect(bidObject1.bidderCode).to.equal('pollux'); - expect(bidObject1.cpm).to.equal(0.5); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.height).to.equal(250); - expect(bidObject1.adUrl).to.have.length.above(1); - }); - - it('should return complete bid response ad (html)', function() { - var params = { - bidderCode: 'pollux', - bids: [{ - placementCode: 'div-gpt-ad-1460505661639-0', - sizes: [[300, 250]], - bidId: '21fe992ca48d55', - bidder: 'pollux', - params: { zone: '1806' } - }] - }; - - var response = { - cpm: 0.5, - width: 300, - height: 250, - callback_id: '21fe992ca48d55', - ad: '<script src="some.ad.url"></script>', - ad_type: 'html', - zone: 1806 - }; - - polluxAdapter.callBids(params); - bidsRequested.push(params); - polluxAdapter.polluxHandler(response); - - sinon.assert.calledOnce(stubAddBidResponse); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - - expect(bidPlacementCode1).to.equal('div-gpt-ad-1460505661639-0'); - expect(bidObject1.bidderCode).to.equal('pollux'); - expect(bidObject1.cpm).to.equal(0.5); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.height).to.equal(250); - expect(bidObject1.ad).to.have.length.above(1); - }); + it('TEST: verify interpretResponse empty', () => { + let bids = spec.interpretResponse(false, {}); + expect(bids).to.not.equal(null); + expect(bids).to.have.lengthOf(0); + bids = spec.interpretResponse([], {}); + expect(bids).to.not.equal(null); + expect(bids).to.have.lengthOf(0); + bids = spec.interpretResponse({}, {}); + expect(bids).to.not.equal(null); + expect(bids).to.have.lengthOf(0); + bids = spec.interpretResponse(null, {}); + expect(bids).to.not.equal(null); + expect(bids).to.have.lengthOf(0); + }); - it('should return no bid response', function() { - var params = { - bidderCode: 'pollux', - bids: [{ - placementCode: 'div-gpt-ad-1460505661639-0', - sizes: [[300, 250]], - bidId: '21fe992ca48d55', - bidder: 'pollux', - params: { zone: '276' } - }] - }; + it('TEST: verify interpretResponse ad_type url', () => { + const serverResponse = { + body: [ + { + bidId: '789s6354sfg856', + cpm: '2.15', + width: '728', + height: '90', + ad: 'http://adn.polluxnetwork.com/zone/276?_plx_prebid=1&_plx_campaign=1125', + ad_type: 'url', + creativeId: '1125', + referrer: 'http://www.example.com' + } + ] + }; + const bids = spec.interpretResponse(serverResponse, {}); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('789s6354sfg856'); + expect(bids[0].cpm).to.equal(2.15); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].ttl).to.equal(3600); + expect(bids[0].creativeId).to.equal('1125'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].currency).to.equal('EUR'); + expect(bids[0].referrer).to.equal('http://www.example.com'); + expect(bids[0].adUrl).to.equal('http://adn.polluxnetwork.com/zone/276?_plx_prebid=1&_plx_campaign=1125'); + expect(bids[0]).to.not.have.property('ad'); + }); - var response = { - cpm: null, - width: null, - height: null, - callback_id: null, - ad: null, - zone: null - }; + it('TEST: verify interpretResponse ad_type html', () => { + const serverResponse = { + body: [ + { + bidId: '789s6354sfg856', + cpm: '2.15', + width: '728', + height: '90', + ad: '<html><h3>I am an ad</h3></html>', + ad_type: 'html', + creativeId: '1125' + } + ] + }; + const bids = spec.interpretResponse(serverResponse, {}); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('789s6354sfg856'); + expect(bids[0].cpm).to.equal(2.15); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].ttl).to.equal(3600); + expect(bids[0].creativeId).to.equal('1125'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].currency).to.equal('EUR'); + expect(bids[0]).to.not.have.property('referrer'); + expect(bids[0]).to.not.have.property('adUrl'); + expect(bids[0].ad).to.equal('<html><h3>I am an ad</h3></html>'); + }); - polluxAdapter.callBids(params); - bidsRequested.push(params); - polluxAdapter.polluxHandler(response); + it('TEST: verify url and query params', () => { + const URL = require('url-parse'); + const querystringify = require('querystringify'); + const request = spec.buildRequests(setup_single_bid); + const parsedUrl = new URL('https:' + request.url); + expect(parsedUrl.origin).to.equal('https://adn.polluxnetwork.com'); + expect(parsedUrl.pathname).to.equal('/prebid/v1'); + expect(parsedUrl).to.have.property('query'); + const parsedQuery = querystringify.parse(parsedUrl.query); + expect(parsedQuery).to.have.property('domain').and.to.have.length.above(1); + }); - sinon.assert.calledOnce(stubAddBidResponse); + it('TEST: verify isBidRequestValid', () => { + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({params: {}})).to.equal(false); + expect(spec.isBidRequestValid(setup_single_bid[0])).to.equal(true); + expect(spec.isBidRequestValid(setup_multi_bid[0])).to.equal(true); + expect(spec.isBidRequestValid(setup_multi_bid[1])).to.equal(true); + }); - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; + it('TEST: verify bidder code', () => { + expect(spec.code).to.equal('pollux'); + }); - expect(bidPlacementCode1).to.equal(''); - expect(bidObject1.bidderCode).to.equal('pollux'); - }); + it('TEST: verify bidder aliases', () => { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('plx'); }); }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 21098a2859f..cdb3113c205 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1,17 +1,20 @@ import { expect } from 'chai'; -import Adapter from 'modules/prebidServerBidAdapter'; -import bidmanager from 'src/bidmanager'; -import CONSTANTS from 'src/constants.json'; +import { PrebidServer as Adapter } from 'modules/prebidServerBidAdapter'; +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'; let CONFIG = { accountId: '1', enabled: true, bidders: ['appnexus'], timeout: 1000, - endpoint: CONSTANTS.S2S.DEFAULT_ENDPOINT + cacheMarkup: 2, + endpoint: 'https://prebid.adnxs.com/pbs/v1/auction' }; const REQUEST = { @@ -25,16 +28,12 @@ const REQUEST = { 'ad_units': [ { 'code': 'div-gpt-ad-1460505748561-0', - 'sizes': [ - { - 'w': 300, - 'h': 250 - }, - { - 'w': 300, - 'h': 600 + 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'banner': { + 'sizes': [[ 300, 250 ], [ 300, 300 ]] } - ], + }, 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', 'bids': [ { @@ -50,6 +49,65 @@ const REQUEST = { ] }; +const 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 ]], + 'mimes': ['video/mp4'] + } + }, + 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', + 'bids': [ + { + 'bid_id': '123', + 'bidder': 'appnexus', + 'params': { 'placementId': '12349520' } + } + ] + } + ] +}; + +const BID_REQUESTS = [ + { + 'bidderCode': 'appnexus', + 'auctionId': '173afb6d132ba3', + 'bidderRequestId': '3d1063078dfcc8', + 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394', + 'member': 123 + }, + 'bid_id': '123', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', + 'sizes': [300, 250], + 'bidId': '259fb43aaa06c1', + 'bidderRequestId': '3d1063078dfcc8', + 'auctionId': '173afb6d132ba3' + } + ], + 'auctionStart': 1510852447530, + 'timeout': 5000, + 'src': 's2s', + 'doneCbCallCount': 0 + } +]; + const RESPONSE = { 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', 'status': 'OK', @@ -73,7 +131,40 @@ const RESPONSE = { 'deal_id': 'test-dealid', 'ad_server_targeting': { 'foo': 'bar' - } + }, + 'cache_id': '7654321', + 'cache_url': 'http://www.test.com/cache?uuid=7654321', + } + ] +}; + +const VIDEO_RESPONSE = { + 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', + 'status': 'OK', + 'bidder_status': [ + { + 'bidder': 'appnexus', + 'response_time_ms': 52, + 'num_bids': 1 + } + ], + 'bids': [ + { + 'bid_id': '123', + 'code': 'div-gpt-ad-1460505748561-0', + 'creative_id': '29681110', + 'bidder': 'appnexus', + 'price': 0.5, + 'adm': '<script type="application/javascript" src="http://nym1-ib.adnxs.com/ab?e=wqT_3QL_Baj_AgAAAwDWAAUBCO-s38cFEJG-p6iRgOfvdhivtLWVpomhsWUgASotCQAAAQII4D8RAQc0AADgPxkAAACA61HgPyEREgApEQmgMPLm_AQ4vgdAvgdIAlDWy5MOWOGASGAAaJFAeP3PBIABAYoBA1VTRJIFBvBSmAGsAqAB-gGoAQGwAQC4AQLAAQPIAQLQAQnYAQDgAQHwAQCKAjp1ZignYScsIDQ5NDQ3MiwgMTQ5MjYzNzI5NSk7dWYoJ3InLCAyOTY4MTExMCwyHgDwnJIC7QEhcHpUNkZ3aTYwSWNFRU5iTGt3NFlBQ0RoZ0Vnd0FEZ0FRQVJJdmdkUTh1YjhCRmdBWVBfX19fOFBhQUJ3QVhnQmdBRUJpQUVCa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRGdQOEVCS1l1SWd3QUE0RF9KQWQ0V2JVTnJmUEVfMlFFQUFBQUFBQUR3UC1BQkFQVUIFD0BKZ0Npb2FvcEFtZ0FnQzFBZwEWBEM5CQjoREFBZ0hJQWdIUUFnSFlBZ0hnQWdEb0FnRDRBZ0NBQXdHUUF3Q1lBd0dvQTdyUWh3US6aAjEhRXduSHU68AAcNFlCSUlBUW8JbARreAFmDQHwui7YAugH4ALH0wHqAg93d3cubnl0aW1lcy5jb23yAhEKBkNQR19JRBIHMTk3NzkzM_ICEAoFQ1BfSUQSBzg1MTM1OTSAAwGIAwGQAwCYAxSgAwGqAwDAA6wCyAMA2APjBuADAOgDAPgDA4AEAJIECS9vcGVucnRiMpgEAKIECzEwLjI0NC4wLjIyqAQAsgQKCAAQABgAIAAwALgEAMAEAMgEANIEDDEwLjMuMTM4LjE0ONoEAggB4AQA8ARBXyCIBQGYBQCgBf8RAZwBqgUkNDM3ZmJiZjUtMzNmNS00ODdhLThlMTYtYTcxMTI5MDNjZmU1&s=b52bf8a6265a78a5969444bc846cc6d0f9f3b489&test=1&referrer=www.nytimes.com&pp=${AUCTION_PRICE}&"></script>', + 'width': 300, + 'height': 250, + 'deal_id': 'test-dealid', + 'ad_server_targeting': { + 'foo': 'bar' + }, + 'media_type': 'video', + 'cache_id': 'video_cache_id', + 'cache_url': 'video_cache_url', } ] }; @@ -173,11 +264,108 @@ const RESPONSE_NO_PBS_COOKIE_ERROR = { }] }; +const RESPONSE_OPENRTB = { + 'id': 'c7dcf14f', + 'seatbid': [ + { + 'bid': [ + { + 'id': '8750901685062148', + 'impid': '123', + 'price': 0.5, + 'adm': '<script src="http://lax1-ib.adnxs.com/ab?e=wqT_3QKgB6CgAwAAAwDWAAUBCJ7kvtMFEPft7JnIuImSdBj87IDv8q21rXcqNgkAAAECCOA_EQEHNAAA4D8ZAAAAgOtR4D8hERIAKREJADERG6Aw8ub8BDi-B0C-B0gCUNbLkw5Y4YBIYABokUB48NIEgAEBigEDVVNEkgUG8FKYAawCoAH6AagBAbABALgBAsABA8gBAtABCdgBAOABAPABAIoCOnVmKCdhJywgNDk0NDcyLCAxNTE3MjY5NTM0KTt1ZigncicsIDI5NjgxMTEwLDIeAPCckgKBAiFqRHF3RUFpNjBJY0VFTmJMa3c0WUFDRGhnRWd3QURnQVFBUkl2Z2RROHViOEJGZ0FZUF9fX184UGFBQndBWGdCZ0FFQmlBRUJrQUVCbUFFQm9BRUJxQUVEc0FFQXVRRXBpNGlEQUFEZ1A4RUJLWXVJZ3dBQTREX0pBVkx3MU5mdl9lMF8yUUVBQUFBQUFBRHdQLUFCQVBVQgUPKEpnQ0FLQUNBTFVDBRAETDAJCPBUTUFDQWNnQ0FkQUNBZGdDQWVBQ0FPZ0NBUGdDQUlBREFaQURBSmdEQWFnRHV0Q0hCTG9ERVdSbFptRjFiSFFqVEVGWU1Ub3pPRFk1mgI5IS1ndndfUTYEAfCENFlCSUlBUW9BRG9SWkdWbVlYVnNkQ05NUVZneE9qTTROamsu2ALoB-ACx9MB6gJHaHR0cDovL3ByZWJpZC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2dwdC9hcHBuZXh1cy10ZXN0Lmh0bWzyAhAKBkFEVl9JRBIGNCXTHPICEQoGQ1BHARM4BzE5Nzc5MzPyAhAKBUNQBRPwljg1MTM1OTSAAwGIAwGQAwCYAxSgAwGqAwDAA6wCyAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAogQMMjE2LjU1LjQ3Ljk0qAQAsgQMCAAQABgAIAAwADgAuAQAwAQAyAQA0gQRZGVmYXVsdCNMQVgxOjM4NjnaBAIIAeAEAPAE1suTDogFAZgFAKAF______8BA7ABqgUkYzdkY2YxNGYtZjliYS00Yzc3LWEzYjQtMjdmNmRmMzkwNjdmwAUAyQVpLhTwP9IFCQkJDFAAANgFAeAFAfAFAfoFBAgAEACQBgA.&s=f4dc8b6fa65845d08f0a87c145e12cb7d6288c2a&referrer=http%3A%2F%2Fprebid.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fappnexus-test.html&pp=${AUCTION_PRICE}"></script>', + 'adid': '29681110', + 'adomain': [ 'appnexus.com' ], + 'iurl': 'http://lax1-ib.adnxs.com/cr?id=2968111', + 'cid': '958', + 'crid': '2968111', + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { 'type': 'banner' }, + 'bidder': { + 'appnexus': { + 'brand_id': 1, + 'auction_id': 3, + 'bidder_id': 2 + } + } + } + } + ], + 'seat': 'appnexus' + }, + ], + 'ext': { + 'responsetimemillis': { + 'appnexus': 8, + } + } +}; + +const RESPONSE_OPENRTB_VIDEO = { + id: 'c7dcf14f', + seatbid: [ + { + bid: [ + { + id: '1987250005171537465', + impid: '/19968336/header-bid-tag-0', + price: 10, + adm: '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><VAST version="3.0"><Ad id="81877115" sequence="0"><Wrapper><AdSystem version="3.0">adnxs</AdSystem><VASTAdTagURI><![CDATA[http://lax1-ib.adnxs.com/ab?e=wqT_3QLZBq]]></VASTAdTagURI><Impression><![CDATA[http://ib.adnxs.com/nop]]></Impression><Creatives><Creative adID="81877115"><Linear></Linear></Creative></Creatives></Wrapper></Ad></VAST>', + adid: '81877115', + adomain: ['appnexus.com'], + iurl: 'http://lax1-ib.adnxs.com/cr?id=81877115', + cid: '3535', + crid: '81877115', + w: 1, + h: 1, + ext: { + prebid: { + type: 'video', + }, + bidder: { + appnexus: { + brand_id: 1, + auction_id: 6673622101799484743, + bidder_id: 2, + bid_ad_type: 1, + }, + }, + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + responsetimemillis: { + appnexus: 81, + }, + }, +}; + +const RESPONSE_UNSUPPORTED_BIDDER = { + 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', + 'status': 'OK', + 'bidder_status': [{ + 'bidder': '33Across', + 'error': 'Unsupported bidder' + }] +}; + describe('S2S Adapter', () => { - let adapter; + let adapter, + addBidResponse = sinon.spy(), + done = sinon.spy(); beforeEach(() => adapter = new Adapter()); + afterEach(() => { + addBidResponse.resetHistory(); + done.resetHistory(); + }); + describe('request function', () => { let xhr; let requests; @@ -186,6 +374,7 @@ describe('S2S Adapter', () => { xhr = sinon.useFakeXMLHttpRequest(); requests = []; xhr.onCreate = request => requests.push(request); + config.resetConfig(); }); afterEach(() => xhr.restore()); @@ -195,16 +384,228 @@ describe('S2S Adapter', () => { }); it('exists converts types', () => { - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + 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); expect(requestBid.ad_units[0].bids[0].params.placementId).to.exist.and.to.be.a('number'); expect(requestBid.ad_units[0].bids[0].params.member).to.exist.and.to.be.a('string'); }); + + it('adds gdpr consent information to ortb2 request depending on module use', () => { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + + let consentConfig = { consentManagement: { cmp: 'iab' }, s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + + let gdprBidRequest = utils.deepClone(BID_REQUESTS); + gdprBidRequest[0].gdprConsent = { + consentString: 'abc123', + gdprApplies: true + }; + + adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.regs.ext.gdpr).is.equal(1); + expect(requestBid.user.ext.consent).is.equal('abc123'); + + config.resetConfig(); + config.setConfig({s2sConfig: CONFIG}); + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + requestBid = JSON.parse(requests[1].requestBody); + + expect(requestBid.regs).to.not.exist; + expect(requestBid.user).to.not.exist; + + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + }); + + it('sets invalid cacheMarkup value to 0', () => { + const s2sConfig = Object.assign({}, CONFIG, { + cacheMarkup: 999 + }); + 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', () => { + let digiTrustObj = { + success: true, + identity: { + privacy: { + optout: false + }, + id: 'testId', + keyv: 'testKeyV' + } + }; + + window.DigiTrust = { + getUser: () => digiTrustObj + }; + + adapter.callBids(REQUEST, BID_REQUESTS, 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 + }); + + digiTrustObj.identity.privacy.optout = true; + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + requestBid = JSON.parse(requests[1].requestBody); + + expect(requestBid.digiTrust).to.not.exist; + + delete window.DigiTrust; + }); + + it('adds device and app objects to request', () => { + const _config = { s2sConfig: CONFIG, + 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.device).to.deep.equal({ + ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', + }); + expect(requestBid.app).to.deep.equal({ + bundle: 'com.test.app', + publisher: {'id': '1'} + }); + }); + + it('adds device and app objects to request for ORTB', () => { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + }); + + 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.device).to.deep.equal({ + ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', + }); + expect(requestBid.app).to.deep.equal({ + bundle: 'com.test.app', + publisher: {'id': '1'} + }); + }); + + it('adds site if app is not present', () => { + 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.site).to.exist.and.to.be.a('object'); + expect(requestBid.site.publisher).to.exist.and.to.be.a('object'); + expect(requestBid.site.page).to.exist.and.to.be.a('string'); + }); + + it('adds appnexus aliases to request', () => { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + }); + config.setConfig({s2sConfig}); + + const aliasBidder = { + bidder: 'brealtime', + params: { placementId: '123456' } + }; + + const request = utils.deepClone(REQUEST); + request.ad_units[0].bids = [aliasBidder]; + + adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.ext).to.deep.equal({ + prebid: { + aliases: { + brealtime: 'appnexus' + } + } + }); + }); + + it('adds dynamic aliases to request', () => { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + }); + config.setConfig({s2sConfig}); + + const alias = 'foobar'; + const aliasBidder = { + bidder: alias, + params: { placementId: '123456' } + }; + + const request = utils.deepClone(REQUEST); + request.ad_units[0].bids = [aliasBidder]; + + // TODO: stub this + $$PREBID_GLOBAL$$.aliasBidder('appnexus', alias); + adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.ext).to.deep.equal({ + prebid: { + aliases: { + [alias]: 'appnexus' + } + } + }); + }); + + it('converts appnexus params to expected format for PBS', () => { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + }); + config.setConfig({s2sConfig}); + + const myRequest = utils.deepClone(REQUEST); + myRequest.ad_units[0].bids[0].params.usePaymentRule = true; + + adapter.callBids(myRequest, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.imp[0].ext.appnexus).to.exist; + expect(requestBid.imp[0].ext.appnexus.placement_id).to.exist.and.to.equal(10433394); + expect(requestBid.imp[0].ext.appnexus.use_pmt_rule).to.exist.and.to.be.true; + expect(requestBid.imp[0].ext.appnexus.member).to.exist; + }); }); describe('response handler', () => { let server; + let logWarnSpy; beforeEach(() => { server = sinon.fakeServer.create(); @@ -212,171 +613,192 @@ describe('S2S Adapter', () => { sinon.stub(utils, 'insertUserSyncIframe'); sinon.stub(utils, 'logError'); sinon.stub(cookie, 'cookieSet'); - sinon.stub(bidmanager, 'addBidResponse'); - sinon.stub(utils, 'getBidderRequestAllAdUnits').returns({ - bids: [{ - bidId: '123', - placementCode: 'div-gpt-ad-1460505748561-0' - }] - }); sinon.stub(utils, 'getBidRequest').returns({ bidId: '123' }); + logWarnSpy = sinon.spy(utils, 'logWarn'); }); afterEach(() => { server.restore(); - bidmanager.addBidResponse.restore(); - utils.getBidderRequestAllAdUnits.restore(); utils.getBidRequest.restore(); utils.triggerPixel.restore(); utils.insertUserSyncIframe.restore(); utils.logError.restore(); cookie.cookieSet.restore(); + logWarnSpy.restore(); }); // TODO: test dependent on pbjs_api_spec. Needs to be isolated it('registers bids', () => { server.respondWith(JSON.stringify(RESPONSE)); - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + sinon.assert.calledOnce(addBidResponse); - const response = bidmanager.addBidResponse.firstCall.args[1]; + 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.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'); + expect(response).to.not.have.property('vastUrl'); + expect(response).to.have.property('serverResponseTimeMs', 52); }); - it('registers no-bid response when ad unit not set', () => { - server.respondWith(JSON.stringify(RESPONSE_NO_BID_NO_UNIT)); + it('registers video bids', () => { + server.respondWith(JSON.stringify(VIDEO_RESPONSE)); - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + sinon.assert.calledOnce(addBidResponse); - const ad_unit_code = bidmanager.addBidResponse.firstCall.args[0]; - expect(ad_unit_code).to.equal('div-gpt-ad-1460505748561-0'); + 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('videoCacheKey', 'video_cache_id'); + expect(response).to.have.property('cache_id', 'video_cache_id'); + expect(response).to.have.property('cache_url', 'video_cache_url'); + expect(response).to.have.property('vastUrl', 'video_cache_url'); + }); + + it('does not call addBidResponse and calls done when ad unit not set', () => { + server.respondWith(JSON.stringify(RESPONSE_NO_BID_NO_UNIT)); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid returned empty or error response'); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); - const bid_request_passed = bidmanager.addBidResponse.firstCall.args[1]; - expect(bid_request_passed).to.have.property('adId', '123'); + sinon.assert.notCalled(addBidResponse); + sinon.assert.calledOnce(done); }); - it('registers no-bid response when server requests cookie sync', () => { + it('does not call addBidResponse and calls done when server requests cookie sync', () => { server.respondWith(JSON.stringify(RESPONSE_NO_COOKIE)); - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const ad_unit_code = bidmanager.addBidResponse.firstCall.args[0]; - expect(ad_unit_code).to.equal('div-gpt-ad-1460505748561-0'); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid returned empty or error response'); - const bid_request_passed = bidmanager.addBidResponse.firstCall.args[1]; - expect(bid_request_passed).to.have.property('adId', '123'); + sinon.assert.notCalled(addBidResponse); + sinon.assert.calledOnce(done); }); - it('registers no-bid response when ad unit is set', () => { + it('does not call addBidResponse and calls done when ad unit is set', () => { server.respondWith(JSON.stringify(RESPONSE_NO_BID_UNIT_SET)); - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - const ad_unit_code = bidmanager.addBidResponse.firstCall.args[0]; - expect(ad_unit_code).to.equal('div-gpt-ad-1460505748561-0'); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid returned empty or error response'); + sinon.assert.notCalled(addBidResponse); + sinon.assert.calledOnce(done); }); - it('registers no-bid response when there are less bids than requests', () => { - utils.getBidderRequestAllAdUnits.restore(); - sinon.stub(utils, 'getBidderRequestAllAdUnits').returns({ - bids: [{ - bidId: '123', - placementCode: 'div-gpt-ad-1460505748561-0' - }, { - bidId: '101111', - placementCode: 'div-gpt-ad-1460505748561-1' - }] - }); - + it('registers successful bids and calls done when there are less bids than requests', () => { server.respondWith(JSON.stringify(RESPONSE)); - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); - sinon.assert.calledTwice(bidmanager.addBidResponse); + sinon.assert.calledOnce(addBidResponse); + sinon.assert.calledOnce(done); - expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('div-gpt-ad-1460505748561-0'); - expect(bidmanager.addBidResponse.secondCall.args[0]).to.equal('div-gpt-ad-1460505748561-1'); + expect(addBidResponse.firstCall.args[0]).to.equal('div-gpt-ad-1460505748561-0'); - expect(bidmanager.addBidResponse.firstCall.args[1]).to.have.property('adId', '123'); - expect(bidmanager.addBidResponse.secondCall.args[1]).to.have.property('adId', '101111'); + expect(addBidResponse.firstCall.args[1]).to.have.property('adId', '123'); - expect(bidmanager.addBidResponse.firstCall.args[1]) + expect(addBidResponse.firstCall.args[1]) .to.have.property('statusMessage', 'Bid available'); - expect(bidmanager.addBidResponse.secondCall.args[1]) - .to.have.property('statusMessage', 'Bid returned empty or error response'); }); it('should have dealId in bidObject', () => { server.respondWith(JSON.stringify(RESPONSE)); - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); - const response = bidmanager.addBidResponse.firstCall.args[1]; + const response = addBidResponse.firstCall.args[1]; expect(response).to.have.property('dealId', 'test-dealid'); }); it('should pass through default adserverTargeting if present in bidObject', () => { server.respondWith(JSON.stringify(RESPONSE)); - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); - const response = bidmanager.addBidResponse.firstCall.args[1]; + const response = addBidResponse.firstCall.args[1]; expect(response).to.have.property('adserverTargeting').that.deep.equals({'foo': 'bar'}); }); + it('registers client user syncs when client bid adapter is present', () => { + let rubiconAdapter = { + registerSyncs: sinon.spy() + }; + sinon.stub(adapterManager, 'getBidAdapter').callsFake(() => rubiconAdapter); + + server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); + + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + + sinon.assert.calledOnce(rubiconAdapter.registerSyncs); + + adapterManager.getBidAdapter.restore(); + }); + + it('registers client user syncs when using OpenRTB endpoint', () => { + let rubiconAdapter = { + registerSyncs: sinon.spy() + }; + sinon.stub(adapterManager, 'getBidAdapter').returns(rubiconAdapter); + + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + }); + config.setConfig({s2sConfig}); + + server.respondWith(JSON.stringify(RESPONSE_OPENRTB)); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + + sinon.assert.calledOnce(rubiconAdapter.registerSyncs); + + adapterManager.getBidAdapter.restore(); + }); + it('registers bid responses when server requests cookie sync', () => { server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + sinon.assert.calledOnce(addBidResponse); - const ad_unit_code = bidmanager.addBidResponse.firstCall.args[0]; + const ad_unit_code = addBidResponse.firstCall.args[0]; expect(ad_unit_code).to.equal('div-gpt-ad-1460505748561-0'); - const response = bidmanager.addBidResponse.firstCall.args[1]; + const response = addBidResponse.firstCall.args[1]; expect(response).to.have.property('statusMessage', 'Bid available'); expect(response).to.have.property('source', 's2s'); - const bid_request_passed = bidmanager.addBidResponse.firstCall.args[1]; + const bid_request_passed = addBidResponse.firstCall.args[1]; expect(bid_request_passed).to.have.property('adId', '123'); }); it('does cookie sync when no_cookie response', () => { server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); sinon.assert.calledOnce(utils.triggerPixel); @@ -388,8 +810,8 @@ describe('S2S Adapter', () => { it('logs error when no_cookie response is missing type or url', () => { server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE_ERROR)); - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); sinon.assert.notCalled(utils.triggerPixel); @@ -400,22 +822,192 @@ describe('S2S Adapter', () => { it('does not call cookieSet cookie sync when no_cookie response && not opted in', () => { server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); - adapter.setConfig(CONFIG); - adapter.callBids(REQUEST); + 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', () => { server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); - let config = Object.assign({ - cookieSet: true + let myConfig = Object.assign({ + cookieSet: true, + cookieSetUrl: 'https://acdn.adnxs.com/cookieset/cs.js' }, CONFIG); - adapter.setConfig(config); - adapter.callBids(REQUEST); + config.setConfig({s2sConfig: myConfig}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); sinon.assert.calledOnce(cookie.cookieSet); }); + + it('handles OpenRTB responses', () => { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + }); + config.setConfig({s2sConfig}); + + server.respondWith(JSON.stringify(RESPONSE_OPENRTB)); + 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('bidderCode', 'appnexus'); + expect(response).to.have.property('adId', '123'); + expect(response).to.have.property('cpm', 0.5); + expect(response).to.have.property('serverResponseTimeMs', 8); + }); + + it('handles OpenRTB video responses', () => { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + }); + config.setConfig({s2sConfig}); + + server.respondWith(JSON.stringify(RESPONSE_OPENRTB_VIDEO)); + 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('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('cpm', 10); + expect(response).to.have.property('serverResponseTimeMs', 81); + }); + + it('should log warning for unsupported bidder', () => { + server.respondWith(JSON.stringify(RESPONSE_UNSUPPORTED_BIDDER)); + + const s2sConfig = Object.assign({}, CONFIG, { + bidders: ['33Across'] + }); + + const _config = { + s2sConfig: s2sConfig, + } + + config.setConfig(_config); + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + + sinon.assert.calledOnce(logWarnSpy); + }); + }); + + describe('s2sConfig', () => { + let logErrorSpy; + + beforeEach(() => { + logErrorSpy = sinon.spy(utils, 'logError'); + }); + + afterEach(() => { + utils.logError.restore(); + }); + + it('should log an error when accountId is missing', () => { + const options = { + enabled: true, + bidders: ['appnexus'], + timeout: 1000, + adapter: 'prebidServer', + endpoint: 'https://prebid.adnxs.com/pbs/v1/auction' + }; + + config.setConfig({ s2sConfig: options }); + sinon.assert.calledOnce(logErrorSpy); + }); + + it('should log an error when bidders is missing', () => { + const options = { + accountId: '1', + enabled: true, + timeout: 1000, + adapter: 's2s', + endpoint: 'https://prebid.adnxs.com/pbs/v1/auction' + }; + + config.setConfig({ s2sConfig: options }); + sinon.assert.calledOnce(logErrorSpy); + }); + + it('should log an error when endpoint is missing', () => { + const options = { + accountId: '1', + bidders: ['appnexus'], + timeout: 1000, + enabled: true, + adapter: 'prebidServer' + }; + + config.setConfig({ s2sConfig: options }); + sinon.assert.calledOnce(logErrorSpy); + }); + + it('should log an error when using an unknown vendor', () => { + const options = { + accountId: '1', + bidders: ['appnexus'], + defaultVendor: 'mytest' + }; + + config.setConfig({ s2sConfig: options }); + sinon.assert.calledOnce(logErrorSpy); + }); + + it('should configure the s2sConfig object with appnexus vendor defaults unless specified by user', () => { + const options = { + accountId: '123', + bidders: ['appnexus'], + defaultVendor: 'appnexus', + timeout: 750 + }; + + config.setConfig({ s2sConfig: options }); + sinon.assert.notCalled(logErrorSpy); + + let vendorConfig = config.getConfig('s2sConfig'); + 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/auction'); + expect(vendorConfig).to.have.property('syncEndpoint', '//prebid.adnxs.com/pbs/v1/cookie_sync'); + expect(vendorConfig).to.have.property('timeout', 750); + }); + + it('should configure the s2sConfig object with rubicon vendor defaults unless specified by user', () => { + const options = { + accountId: 'abc', + bidders: ['rubicon'], + defaultVendor: 'rubicon', + timeout: 750 + }; + + config.setConfig({ s2sConfig: options }); + sinon.assert.notCalled(logErrorSpy); + + let vendorConfig = config.getConfig('s2sConfig'); + 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/auction'); + expect(vendorConfig).to.have.property('syncEndpoint', '//prebid-server.rubiconproject.com/cookie_sync'); + expect(vendorConfig).to.have.property('timeout', 750); + }); }); }); diff --git a/test/spec/modules/pubCommonId_spec.js b/test/spec/modules/pubCommonId_spec.js new file mode 100644 index 00000000000..50ca4616a4b --- /dev/null +++ b/test/spec/modules/pubCommonId_spec.js @@ -0,0 +1,192 @@ +import { + requestBidHook, + getCookie, + setCookie, + setConfig, + isPubcidEnabled, + getExpInterval, + initPubcid } 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'; + +var assert = require('chai').assert; +var expect = require('chai').expect; + +const COOKIE_NAME = '_pubcid'; +const TIMEOUT = 2000; + +describe('Publisher Common ID', function () { + describe('Decorate adUnits', function () { + before(function() { + window.document.cookie = COOKIE_NAME + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + }); + + it('Check same cookie', function () { + let adUnits1 = getAdUnits(); + let adUnits2 = getAdUnits(); + let innerAdUnits1; + let innerAdUnits2; + let pubcid = getCookie(COOKIE_NAME); + + expect(pubcid).to.be.null; // there should be no cookie initially + + requestBidHook({adUnits: adUnits1}, (config) => { innerAdUnits1 = config.adUnits }); + pubcid = getCookie(COOKIE_NAME); // cookies is created after requestbidHook + + innerAdUnits1.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid.crumbs.pubcid).to.equal(pubcid); + }); + }); + requestBidHook({adUnits: adUnits2}, (config) => { innerAdUnits2 = config.adUnits }); + assert.deepEqual(innerAdUnits1, innerAdUnits2); + }); + + it('Check different cookies', function () { + let adUnits1 = getAdUnits(); + let adUnits2 = getAdUnits(); + let innerAdUnits1; + let innerAdUnits2; + let pubcid1; + let pubcid2; + + requestBidHook({adUnits: adUnits1}, (config) => { innerAdUnits1 = config.adUnits }); + pubcid1 = getCookie(COOKIE_NAME); // get first cookie + setCookie(COOKIE_NAME, '', -1); // erase cookie + + innerAdUnits1.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid.crumbs.pubcid).to.equal(pubcid1); + }); + }); + + requestBidHook({adUnits: adUnits2}, (config) => { innerAdUnits2 = config.adUnits }); + pubcid2 = getCookie(COOKIE_NAME); // get second cookie + + innerAdUnits2.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid.crumbs.pubcid).to.equal(pubcid2); + }); + }); + + expect(pubcid1).to.not.equal(pubcid2); + }); + + it('Check new cookie', function () { + let adUnits = getAdUnits(); + let innerAdUnits; + let pubcid = utils.generateUUID(); + + setCookie(COOKIE_NAME, pubcid, 600); + requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); + innerAdUnits.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid.crumbs.pubcid).to.equal(pubcid); + }); + }); + }); + }); + + describe('Configuration', function () { + 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); + innerAdUnits.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid.crumbs.pubcid).to.equal(pubcid); + }); + }); + }); + + 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; + assert.deepEqual(innerAdUnits, unmodified); + setConfig({enable: true}); // reset + requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); + innerAdUnits.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.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 }); + innerAdUnits.every((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.property('crumbs.pubcid'); + }); + }) + }); + }); + + describe('Invoking requestBid', function () { + let createAuctionStub; + let adUnits; + let adUnitCodes; + let capturedReqs; + let sampleSpec = { + code: 'sampleBidder', + isBidRequestValid: () => {}, + buildRequest: (reqs) => {}, + interpretResponse: () => {}, + getUserSyncs: () => {} + }; + + beforeEach(() => { + adUnits = [{ + code: 'adUnit-code', + mediaTypes: { + banner: {}, + native: {}, + }, + sizes: [[300, 200], [300, 600]], + bids: [ + {bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}} + ] + }]; + adUnitCodes = ['adUnit-code']; + let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: TIMEOUT}); + createAuctionStub = sinon.stub(auctionModule, 'newAuction'); + createAuctionStub.returns(auction); + initPubcid(); + registerBidder(sampleSpec); + }); + + afterEach(() => { + auctionModule.newAuction.restore(); + }); + + it('test hook', function() { + $$PREBID_GLOBAL$$.requestBids({adUnits}); + adUnits.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.property('crumbs.pubcid'); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/pubgearsBidAdapter_spec.js b/test/spec/modules/pubgearsBidAdapter_spec.js deleted file mode 100644 index 81f890e0dfd..00000000000 --- a/test/spec/modules/pubgearsBidAdapter_spec.js +++ /dev/null @@ -1,287 +0,0 @@ -import { expect } from 'chai'; -import Adapter from 'modules/pubgearsBidAdapter' -import bidmanager from 'src/bidmanager' - -describe('PubGearsAdapter', () => { - var adapter, mockScript, - params = { - bids: [] - } - - beforeEach(() => { - adapter = new Adapter() - mockScript = document.createElement('script') - sinon.spy(mockScript, 'setAttribute') - }) - - describe('request function', () => { - beforeEach(() => { - sinon.spy(document, 'createElement') - }) - - afterEach(() => { - document.createElement.restore && document.createElement.restore() - var s = document.getElementById('pg-header-tag') - if (s) { s.parentNode.removeChild(s) } - }) - - it('has `#callBids()` method', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function') - }) - - it('requires bids to make script', () => { - adapter.callBids({bids: []}) - expect(document.createElement.notCalled).to.be.ok - }) - - it('creates script when passed bids', () => { - adapter.callBids({ - bidderCode: 'pubgears', - bids: [ - { - bidder: 'pubgears', - sizes: [ [300, 250] ], - adUnitCode: 'foo123/header-bid-tag', - params: { - publisherName: 'integration', - pubZone: 'testpub.com/combined' - } - } - ] - }) - - sinon.assert.calledWith(document.createElement, 'script') - }) - - it('should assign attributes to script', () => { - adapter.callBids({ - bidderCode: 'pubgears', - bids: [ - { - bidder: 'pubgears', - sizes: [ [300, 250] ], - adUnitCode: 'foo123/header-bid-tag', - params: { - publisherName: 'integration', - pubZone: 'testpub.com/combined' - } - }, - { - bidder: 'pubgears', - sizes: [ [160, 600] ], - adUnitCode: 'foo123/header-bid-tag', - params: { - publisherName: 'integration', - pubZone: 'testpub.com/combined' - } - } - ] - }) - var script = document.createElement.returnValues[0] - var slots = script.getAttribute('data-bsm-slot-list') - expect(slots).to.equal('testpub.com/combined@300x250 testpub.com/combined@160x600') - expect(script.getAttribute('data-bsm-flag')).to.equal('true') - expect(script.getAttribute('data-bsm-pub')).to.equal('integration') - expect(script.getAttribute('src')).to.equal('//c.pubgears.com/tags/h') - expect(script.id).to.equal('pg-header-tag') - }) - - it('should reuse existing script when called twice', () => { - var params = { - bidderCode: 'pubgears', - bids: [ - { - bidder: 'pubgears', - sizes: [ [300, 250] ], - adUnitCode: 'foo123/header-bid-tag', - params: { - publisherName: 'integration', - pubZone: 'testpub.com/combined' - } - }, - { - bidder: 'pubgears', - sizes: [ [160, 600] ], - adUnitCode: 'foo123/header-bid-tag', - params: { - publisherName: 'integration', - pubZone: 'testpub.com/combined' - } - } - ] - } - adapter.callBids(params) - expect(document.createElement.calledOnce).to.be.true - adapter.callBids(params) - expect(document.createElement.calledOnce).to.be.true - }) - - it('should register event listeners', () => { - var script = document.createElement('script') - script.id = 'pg-header-tag' - var spy = sinon.spy(script, 'addEventListener') - document.body.appendChild(script) - var params = { - bidderCode: 'pubgears', - bids: [ - { - bidder: 'pubgears', - sizes: [ [300, 250] ], - adUnitCode: 'foo123/header-bid-tag', - params: { - publisherName: 'integration', - pubZone: 'testpub.com/combined' - } - }, - { - bidder: 'pubgears', - sizes: [ [160, 600] ], - adUnitCode: 'foo123/header-bid-tag', - params: { - publisherName: 'integration', - pubZone: 'testpub.com/combined' - } - } - ] - } - adapter.callBids(params) - - expect(spy.calledWith('onBidResponse')).to.be.ok - expect(spy.calledWith('onResourceComplete')).to.be.ok - }) - }) - - describe('bids received', () => { - beforeEach(() => { - sinon.spy(bidmanager, 'addBidResponse') - }) - - afterEach(() => { - bidmanager.addBidResponse.restore() - }) - - it('should call bidManager.addBidResponse() when bid received', () => { - var options = { - bubbles: false, - cancelable: false, - detail: { - gross_price: 1000, - resource: { - position: 'atf', - pub_zone: 'testpub.com/combined', - size: '300x250' - } - } - } - - adapter.callBids({ - bidderCode: 'pubgears', - bids: [ - { - bidder: 'pubgears', - sizes: [ [300, 250] ], - adUnitCode: 'foo123/header-bid-tag', - params: { - publisherName: 'integration', - pubZone: 'testpub.com/combined' - } - }, - { - bidder: 'pubgears', - sizes: [ [160, 600] ], - adUnitCode: 'foo123/header-bid-tag', - params: { - publisherName: 'integration', - pubZone: 'testpub.com/combined' - } - } - ] - - }) - var script = document.getElementById('pg-header-tag') - var event = new CustomEvent('onBidResponse', options) - script.dispatchEvent(event) - - expect(bidmanager.addBidResponse.calledOnce).to.be.ok - }) - - it('should send correct bid response object when receiving onBidResponse event', () => { - expect(bidmanager.addBidResponse.calledOnce).to.not.be.ok - var bid = { - bidder: 'pubgears', - sizes: [ [300, 250] ], - adUnitCode: 'foo123/header-bid-tag', - params: { - publisherName: 'integration', - pubZone: 'testpub.com/combined' - } - } - - adapter.callBids({ - bidderCode: 'pubgears', - bids: [ bid ] - }) - - var options = { - bubbles: false, - cancelable: false, - detail: { - gross_price: 1000, - resource: { - position: 'atf', - pub_zone: 'testpub.com/combined', - size: '300x250' - } - } - } - var script = document.getElementById('pg-header-tag') - var event = new CustomEvent('onBidResponse', options) - script.dispatchEvent(event) - - var args = bidmanager.addBidResponse.getCall(1).args - expect(args).to.have.length(2) - var bidResponse = args[1] - expect(bidResponse.ad).to.contain(bid.params.pubZone) - }) - - it('should send $0 bid as no-bid response', () => { - var bid = { - bidder: 'pubgears', - sizes: [ [300, 250] ], - adUnitCode: 'foo123/header-bid-tag', - params: { - publisherName: 'integration', - pubZone: 'testpub.com/combined' - } - } - - adapter.callBids({ - bidderCode: 'pubgears', - bids: [ bid ] - }) - - var options = { - bubbles: false, - cancelable: false, - detail: { - gross_price: 0, - resource: { - position: 'atf', - pub_zone: 'testpub.com/combined', - size: '300x250' - } - } - } - var script = document.getElementById('pg-header-tag') - var event = new CustomEvent('onBidResponse', options) - - bidmanager.addBidResponse.reset() - script.dispatchEvent(event) - - var args = bidmanager.addBidResponse.getCall(1).args - var bidResponse = args[1] - expect(bidResponse).to.be.a('object') - expect(bidResponse.getStatusCode()).to.equal(2) - }) - }) -}) diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js new file mode 100644 index 00000000000..7ea10315a4e --- /dev/null +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -0,0 +1,237 @@ +import {expect} from 'chai'; +import {spec} from 'modules/pubmaticBidAdapter'; +import * as utils from 'src/utils'; +const constants = require('src/constants.json'); + +describe('PubMatic adapter', () => { + let bidRequests; + let bidResponses; + + beforeEach(() => { + bidRequests = [ + { + 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' + }, + 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' + } + ]; + + bidResponses = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': 'image3.pubmatic.com Layer based creative', + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 6 + } + }] + }] + } + }; + }); + + describe('implementation', () => { + describe('Bid validations', () => { + it('valid bid case', () => { + let validBid = { + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('invalid bid case: publisherId not passed', () => { + let validBid = { + bidder: 'pubmatic', + params: { + adSlot: '/15671365/DMDemo@300x250:0' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + + it('invalid bid case: publisherId is not string', () => { + let validBid = { + bidder: 'pubmatic', + params: { + publisherId: 301, + adSlot: '/15671365/DMDemo@300x250:0' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + + it('invalid bid case: adSlot not passed', () => { + let validBid = { + bidder: 'pubmatic', + params: { + publisherId: '301' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + + it('invalid bid case: adSlot is not string', () => { + let validBid = { + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: 15671365 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + }); + + describe('Request formation', () => { + it('Endpoint checking', () => { + let request = spec.buildRequests(bidRequests); + expect(request.url).to.equal('//hbopenbid.pubmatic.com/translator?source=prebid-client'); + expect(request.method).to.equal('POST'); + }); + + it('Request params check', () => { + 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.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 + + 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 params check with GDPR Consent', () => { + 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 + + 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('invalid adslot', () => { + bidRequests[0].params.adSlot = '/15671365/DMDemo'; + let request = spec.buildRequests(bidRequests); + expect(request).to.equal(undefined); + }); + }); + + describe('Response checking', () => { + it('should check for valid response values', () => { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); + expect(response[0].cpm).to.equal((bidResponses.body.seatbid[0].bid[0].price).toFixed(2)); + expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); + if (bidResponses.body.seatbid[0].bid[0].crid) { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); + } else { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + } + expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); + expect(response[0].currency).to.equal('USD'); + expect(response[0].netRevenue).to.equal(false); + expect(response[0].ttl).to.equal(300); + expect(response[0].referrer).to.include(utils.getTopWindowUrl()); + expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); + }); + + it('should check for dealChannel value selection', () => { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].dealChannel).to.equal('PMPG'); + }); + + it('should check for unexpected dealChannel value selection', () => { + let request = spec.buildRequests(bidRequests); + let updateBiResponse = bidResponses; + updateBiResponse.body.seatbid[0].bid[0].ext.deal_channel = 11; + + let response = spec.interpretResponse(updateBiResponse, request); + + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].dealChannel).to.equal(null); + }); + }); + }); +}); diff --git a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js index 4c3919172d8..ffb8d3c0570 100644 --- a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js @@ -4,7 +4,26 @@ let adaptermanager = require('src/adaptermanager'); let constants = require('src/constants.json'); describe('PubWise Prebid Analytics', function () { + let xhr; + + before(() => { + xhr = sinon.useFakeXMLHttpRequest(); + }); + + after(() => { + xhr.restore(); + pubwiseAnalytics.disableAnalytics(); + }); + describe('enableAnalytics', function () { + beforeEach(() => { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(() => { + events.getEvents.restore(); + }); + it('should catch all events', function () { sinon.spy(pubwiseAnalytics, 'track'); diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 07639310c36..709dbeb76a2 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -1,163 +1,297 @@ +/* eslint dot-notation:0, quote-props:0 */ import {expect} from 'chai'; -import PulsePointAdapter from '../../../modules/pulsepointBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import adLoader from '../../../src/adloader'; +import {spec} from 'modules/pulsepointBidAdapter'; +import {getTopWindowLocation} from 'src/utils'; +import {newBidder} from 'src/adapters/bidderFactory'; describe('PulsePoint Adapter Tests', () => { - let pulsepointAdapter = new PulsePointAdapter(); - let slotConfigs; - let requests = []; - let responses = {}; - - function initPulsepointLib() { - /* Mocked PulsePoint library */ - window.pp = { - requestActions: { - BID: 0 + const slotConfigs = [{ + placementCode: '/DfpAccount1/slot1', + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + cf: '300x250' + } + }, { + placementCode: '/DfpAccount2/slot2', + bidId: 'bid23456', + params: { + cp: 'p10000', + ct: 't20000', + cf: '728x90' + } + }]; + const nativeSlotConfig = [{ + placementCode: '/DfpAccount1/slot3', + bidId: 'bid12345', + nativeParams: { + title: { required: true, len: 200 }, + image: { wmin: 100 }, + sponsoredBy: { } + }, + params: { + cp: 'p10000', + ct: 't10000' + } + }]; + const appSlotConfig = [{ + placementCode: '/DfpAccount1/slot3', + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + app: { + bundle: 'com.pulsepoint.apps', + storeUrl: 'http://pulsepoint.com/apps', + domain: 'pulsepoint.com', } - }; - /* Ad object */ - window.pp.Ad = function(config) { - this.display = function() { - requests.push(config); - config.callback(responses[config.ct]); - }; - }; - } + } + }]; - function resetPulsepointLib() { - window.pp = undefined; - } + it('Verify build request', () => { + const request = spec.buildRequests(slotConfigs); + expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(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.imp).to.have.lengthOf(2); + // device object + expect(ortbRequest.device).to.not.equal(null); + expect(ortbRequest.device.ua).to.equal(navigator.userAgent); + // slot 1 + expect(ortbRequest.imp[0].tagid).to.equal('t10000'); + expect(ortbRequest.imp[0].banner).to.not.equal(null); + expect(ortbRequest.imp[0].banner.w).to.equal(300); + expect(ortbRequest.imp[0].banner.h).to.equal(250); + // slot 2 + expect(ortbRequest.imp[1].tagid).to.equal('t20000'); + expect(ortbRequest.imp[1].banner).to.not.equal(null); + expect(ortbRequest.imp[1].banner.w).to.equal(728); + expect(ortbRequest.imp[1].banner.h).to.equal(90); + }); - beforeEach(() => { - initPulsepointLib(); - sinon.stub(bidManager, 'addBidResponse'); - sinon.stub(adLoader, 'loadScript'); + it('Verify parse response', () => { + const request = spec.buildRequests(slotConfigs); + const ortbRequest = JSON.parse(request.data); + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'This is an Ad' + }] + }] + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + expect(bids).to.have.lengthOf(1); + // verify first bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.ad).to.equal('This is an Ad'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.adId).to.equal('bid12345'); + expect(bid.creative_id).to.equal('bid12345'); + expect(bid.creativeId).to.equal('bid12345'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(20); + }); - slotConfigs = { - bids: [ - { - placementCode: '/DfpAccount1/slot1', - bidder: 'pulsepoint', - bidId: 'bid12345', - params: { - cp: 'p10000', - ct: 't10000', - cf: '300x250', - param1: 'value1', - param2: 2 - } - }, { - placementCode: '/DfpAccount2/slot2', - bidder: 'pulsepoint', - bidId: 'bid23456', - params: { - cp: 'p20000', - ct: 't20000', - cf: '728x90' + it('Verify use ttl in ext', () => { + const request = spec.buildRequests(slotConfigs); + const ortbRequest = JSON.parse(request.data); + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'This is an Ad', + ext: { + ttl: 30, + netRevenue: false, + currency: 'INR' } - } - ] + }] + }] }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + expect(bids).to.have.lengthOf(1); + // verify first bid + const bid = bids[0]; + expect(bid.ttl).to.equal(30); + expect(bid.netRevenue).to.equal(false); + expect(bid.currency).to.equal('INR'); }); - afterEach(() => { - bidManager.addBidResponse.restore(); - adLoader.loadScript.restore(); - requests = []; - responses = {}; + it('Verify full passback', () => { + const request = spec.buildRequests(slotConfigs); + const bids = spec.interpretResponse({ body: null }, request) + expect(bids).to.have.lengthOf(0); }); - it('Verify requests sent to PulsePoint library', () => { - pulsepointAdapter.callBids(slotConfigs); - expect(requests).to.have.length(2); - // slot 1 - expect(requests[0].cp).to.equal('p10000'); - expect(requests[0].ct).to.equal('t10000'); - expect(requests[0].cf).to.equal('300x250'); - expect(requests[0].ca).to.equal(0); - expect(requests[0].cn).to.equal(1); - expect(requests[0].cu).to.equal('http://bid.contextweb.com/header/tag'); - expect(requests[0].adUnitId).to.equal('/DfpAccount1/slot1'); - expect(requests[0]).to.have.property('callback'); - expect(requests[0].param1).to.equal('value1'); - expect(requests[0].param2).to.equal(2); - // //slot 2 - expect(requests[1].cp).to.equal('p20000'); - expect(requests[1].ct).to.equal('t20000'); - expect(requests[1].cf).to.equal('728x90'); - expect(requests[1].ca).to.equal(0); - expect(requests[1].cn).to.equal(1); - expect(requests[1].cu).to.equal('http://bid.contextweb.com/header/tag'); - expect(requests[1].adUnitId).to.equal('/DfpAccount2/slot2'); - expect(requests[1]).to.have.property('callback'); + it('Verify Native request', () => { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + // native impression + expect(ortbRequest.imp[0].tagid).to.equal('t10000'); + expect(ortbRequest.imp[0].banner).to.equal(null); + const nativePart = ortbRequest.imp[0]['native']; + expect(nativePart).to.not.equal(null); + expect(nativePart.ver).to.equal('1.1'); + expect(nativePart.request).to.not.equal(null); + // native request assets + const nativeRequest = JSON.parse(ortbRequest.imp[0]['native'].request); + expect(nativeRequest).to.not.equal(null); + expect(nativeRequest.assets).to.have.lengthOf(3); + // title asset + expect(nativeRequest.assets[0].id).to.equal(1); + expect(nativeRequest.assets[0].required).to.equal(1); + expect(nativeRequest.assets[0].title).to.not.equal(null); + expect(nativeRequest.assets[0].title.len).to.equal(200); + // data asset + expect(nativeRequest.assets[1].id).to.equal(2); + expect(nativeRequest.assets[1].required).to.equal(0); + expect(nativeRequest.assets[1].title).to.be.undefined; + expect(nativeRequest.assets[1].data).to.not.equal(null); + expect(nativeRequest.assets[1].data.type).to.equal(1); + expect(nativeRequest.assets[1].data.len).to.equal(50); + // image asset + expect(nativeRequest.assets[2].id).to.equal(3); + expect(nativeRequest.assets[2].required).to.equal(0); + expect(nativeRequest.assets[2].title).to.be.undefined; + expect(nativeRequest.assets[2].img).to.not.equal(null); + expect(nativeRequest.assets[2].img.wmin).to.equal(100); + expect(nativeRequest.assets[2].img.hmin).to.equal(150); + expect(nativeRequest.assets[2].img.type).to.equal(3); }); - it('Verify bid', () => { - responses['t10000'] = { - html: 'This is an Ad', - bidCpm: 1.25 + it('Verify Native response', () => { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + const nativeResponse = { + 'native': { + assets: [ + { title: { text: 'Ad Title' } }, + { data: { type: 1, value: 'Sponsored By: Brand' } }, + { img: { type: 3, url: 'http://images.cdn.brand.com/123' } } + ], + link: { url: 'http://brand.clickme.com/' }, + imptrackers: ['http://imp1.trackme.com/', 'http://imp1.contextweb.com/'] + } + }; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: JSON.stringify(nativeResponse) + }] + }] }; - pulsepointAdapter.callBids(slotConfigs); - let placement = bidManager.addBidResponse.firstCall.args[0]; - let bid = bidManager.addBidResponse.firstCall.args[1]; - expect(placement).to.equal('/DfpAccount1/slot1'); - expect(bid.bidderCode).to.equal('pulsepoint'); + const bids = spec.interpretResponse({ body: ortbResponse }, request); + // verify bid + const bid = bids[0]; expect(bid.cpm).to.equal(1.25); - expect(bid.ad).to.equal('This is an Ad'); - expect(bid.width).to.equal('300'); - expect(bid.height).to.equal('250'); expect(bid.adId).to.equal('bid12345'); + expect(bid.ad).to.be.undefined; + expect(bid.mediaType).to.equal('native'); + const nativeBid = bid['native']; + expect(nativeBid).to.not.equal(null); + expect(nativeBid.title).to.equal('Ad Title'); + expect(nativeBid.sponsoredBy).to.equal('Sponsored By: Brand'); + expect(nativeBid.image).to.equal('http://images.cdn.brand.com/123'); + expect(nativeBid.clickUrl).to.equal(encodeURIComponent('http://brand.clickme.com/')); + expect(nativeBid.impressionTrackers).to.have.lengthOf(2); + expect(nativeBid.impressionTrackers[0]).to.equal('http://imp1.trackme.com/'); + expect(nativeBid.impressionTrackers[1]).to.equal('http://imp1.contextweb.com/'); }); - it('Verify passback', () => { - pulsepointAdapter.callBids(slotConfigs); - let placement = bidManager.addBidResponse.firstCall.args[0]; - let bid = bidManager.addBidResponse.firstCall.args[1]; - expect(placement).to.equal('/DfpAccount1/slot1'); - expect(bid.bidderCode).to.equal('pulsepoint'); - expect(bid).to.not.have.property('ad'); - expect(bid).to.not.have.property('cpm'); - expect(bid.adId).to.equal('bid12345'); + it('Verifies bidder code', () => { + expect(spec.code).to.equal('pulsepoint'); }); - it('Verify PulsePoint library is downloaded if nessesary', () => { - resetPulsepointLib(); - pulsepointAdapter.callBids(slotConfigs); - let libraryLoadCall = adLoader.loadScript.firstCall.args[0]; - let callback = adLoader.loadScript.firstCall.args[1]; - expect(libraryLoadCall).to.equal('http://tag-st.contextweb.com/getjs.static.js'); - expect(callback).to.be.a('function'); + it('Verifies bidder aliases', () => { + expect(spec.aliases).to.have.lengthOf(2); + expect(spec.aliases[0]).to.equal('pulseLite'); + expect(spec.aliases[1]).to.equal('pulsepointLite'); }); - it('Verify Bids get processed after PulsePoint library downloads', () => { - resetPulsepointLib(); - pulsepointAdapter.callBids(slotConfigs); - let callback = adLoader.loadScript.firstCall.args[1]; - let bidCall = bidManager.addBidResponse.firstCall; - expect(callback).to.be.a('function'); - expect(bidCall).to.be.a('null'); - // the library load should initialize pulsepoint lib - initPulsepointLib(); - callback(); - expect(requests.length).to.equal(2); - bidCall = bidManager.addBidResponse.firstCall; - expect(bidCall).to.be.a('object'); - expect(bidCall.args[0]).to.equal('/DfpAccount1/slot1'); - expect(bidCall.args[1]).to.be.a('object'); + it('Verifies supported media types', () => { + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[1]).to.equal('native'); }); - // related to issue https://github.com/prebid/Prebid.js/issues/866 - it('Verify Passbacks when window.pp is not available', () => { - window.pp = function() {}; - pulsepointAdapter.callBids(slotConfigs); - let placement = bidManager.addBidResponse.firstCall.args[0]; - let bid = bidManager.addBidResponse.firstCall.args[1]; - // verify that we passed back without exceptions, should window.pp be already taken. - expect(placement).to.equal('/DfpAccount1/slot1'); - expect(bid.bidderCode).to.equal('pulsepoint'); - expect(bid).to.not.have.property('ad'); - expect(bid).to.not.have.property('cpm'); - expect(bid.adId).to.equal('bid12345'); + it('Verifies if bid request valid', () => { + expect(spec.isBidRequestValid(slotConfigs[0])).to.equal(true); + expect(spec.isBidRequestValid(slotConfigs[1])).to.equal(true); + expect(spec.isBidRequestValid(nativeSlotConfig[0])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect(spec.isBidRequestValid({ params: { ct: 123 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { cp: 123 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { ct: 123, cp: 234 } })).to.equal(true); + }); + + it('Verifies sync options', () => { + expect(spec.getUserSyncs({})).to.be.undefined; + expect(spec.getUserSyncs({ iframeEnabled: false })).to.be.undefined; + 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('//bh.contextweb.com/visitormatch'); + }); + + it('Verifies image pixel sync', () => { + const options = spec.getUserSyncs({ pixelEnabled: true }); + 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'); + }); + + it('Verify app requests', () => { + const request = spec.buildRequests(appSlotConfig); + const ortbRequest = JSON.parse(request.data); + // site object + expect(ortbRequest.site).to.equal(null); + expect(ortbRequest.app).to.not.be.null; + expect(ortbRequest.app.publisher).to.not.equal(null); + expect(ortbRequest.app.publisher.id).to.equal('p10000'); + expect(ortbRequest.app.bundle).to.equal('com.pulsepoint.apps'); + expect(ortbRequest.app.storeurl).to.equal('http://pulsepoint.com/apps'); + expect(ortbRequest.app.domain).to.equal('pulsepoint.com'); + }); + + it('Verify GDPR', () => { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'serialized_gpdr_data' + } + }; + const request = spec.buildRequests(slotConfigs, bidderRequest); + expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + // user object + expect(ortbRequest.user).to.not.equal(null); + expect(ortbRequest.user.ext).to.not.equal(null); + expect(ortbRequest.user.ext.consent).to.equal('serialized_gpdr_data'); + // regs object + expect(ortbRequest.regs).to.not.equal(null); + expect(ortbRequest.regs.ext).to.not.equal(null); + expect(ortbRequest.regs.ext.gdpr).to.equal(1); }); }); diff --git a/test/spec/modules/pulsepointLiteBidAdapter_spec.js b/test/spec/modules/pulsepointLiteBidAdapter_spec.js deleted file mode 100644 index 96f5c7a8d1f..00000000000 --- a/test/spec/modules/pulsepointLiteBidAdapter_spec.js +++ /dev/null @@ -1,247 +0,0 @@ -/* eslint dot-notation:0, quote-props:0 */ -import {expect} from 'chai'; -import {spec} from 'modules/pulsepointLiteBidAdapter'; -import bidManager from 'src/bidmanager'; -import {getTopWindowLocation} from 'src/utils'; -import {newBidder} from 'src/adapters/bidderFactory'; - -describe('PulsePoint Lite Adapter Tests', () => { - const slotConfigs = [{ - placementCode: '/DfpAccount1/slot1', - bidId: 'bid12345', - params: { - cp: 'p10000', - ct: 't10000', - cf: '300x250' - } - }, { - placementCode: '/DfpAccount2/slot2', - bidId: 'bid23456', - params: { - cp: 'p10000', - ct: 't20000', - cf: '728x90' - } - }]; - const nativeSlotConfig = [{ - placementCode: '/DfpAccount1/slot3', - bidId: 'bid12345', - nativeParams: { - title: { required: true, len: 200 }, - image: { wmin: 100 }, - sponsoredBy: { } - }, - params: { - cp: 'p10000', - ct: 't10000' - } - }]; - const appSlotConfig = [{ - placementCode: '/DfpAccount1/slot3', - bidId: 'bid12345', - params: { - cp: 'p10000', - ct: 't10000', - app: { - bundle: 'com.pulsepoint.apps', - storeUrl: 'http://pulsepoint.com/apps', - domain: 'pulsepoint.com', - } - } - }]; - - it('Verify build request', () => { - const request = spec.buildRequests(slotConfigs); - expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); - expect(request.method).to.equal('POST'); - const ortbRequest = JSON.parse(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.imp).to.have.lengthOf(2); - // device object - expect(ortbRequest.device).to.not.equal(null); - expect(ortbRequest.device.ua).to.equal(navigator.userAgent); - // slot 1 - expect(ortbRequest.imp[0].tagid).to.equal('t10000'); - expect(ortbRequest.imp[0].banner).to.not.equal(null); - expect(ortbRequest.imp[0].banner.w).to.equal(300); - expect(ortbRequest.imp[0].banner.h).to.equal(250); - // slot 2 - expect(ortbRequest.imp[1].tagid).to.equal('t20000'); - expect(ortbRequest.imp[1].banner).to.not.equal(null); - expect(ortbRequest.imp[1].banner.w).to.equal(728); - expect(ortbRequest.imp[1].banner.h).to.equal(90); - }); - - it('Verify parse response', () => { - const request = spec.buildRequests(slotConfigs); - const ortbRequest = JSON.parse(request.data); - const ortbResponse = { - seatbid: [{ - bid: [{ - impid: ortbRequest.imp[0].id, - price: 1.25, - adm: 'This is an Ad' - }] - }] - }; - const bids = spec.interpretResponse(ortbResponse, request); - expect(bids).to.have.lengthOf(1); - // verify first bid - const bid = bids[0]; - expect(bid.cpm).to.equal(1.25); - expect(bid.ad).to.equal('This is an Ad'); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.adId).to.equal('bid12345'); - expect(bid.creative_id).to.equal('bid12345'); - expect(bid.creativeId).to.equal('bid12345'); - }); - - it('Verify full passback', () => { - const request = spec.buildRequests(slotConfigs); - const bids = spec.interpretResponse(null, request) - expect(bids).to.have.lengthOf(0); - }); - - it('Verify Native request', () => { - const request = spec.buildRequests(nativeSlotConfig); - expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); - expect(request.method).to.equal('POST'); - const ortbRequest = JSON.parse(request.data); - // native impression - expect(ortbRequest.imp[0].tagid).to.equal('t10000'); - expect(ortbRequest.imp[0].banner).to.equal(null); - const nativePart = ortbRequest.imp[0]['native']; - expect(nativePart).to.not.equal(null); - expect(nativePart.ver).to.equal('1.1'); - expect(nativePart.request).to.not.equal(null); - // native request assets - const nativeRequest = JSON.parse(ortbRequest.imp[0]['native'].request); - expect(nativeRequest).to.not.equal(null); - expect(nativeRequest.assets).to.have.lengthOf(3); - // title asset - expect(nativeRequest.assets[0].id).to.equal(1); - expect(nativeRequest.assets[0].required).to.equal(1); - expect(nativeRequest.assets[0].title).to.not.equal(null); - expect(nativeRequest.assets[0].title.len).to.equal(200); - // data asset - expect(nativeRequest.assets[1].id).to.equal(2); - expect(nativeRequest.assets[1].required).to.equal(0); - expect(nativeRequest.assets[1].title).to.be.undefined; - expect(nativeRequest.assets[1].data).to.not.equal(null); - expect(nativeRequest.assets[1].data.type).to.equal(1); - expect(nativeRequest.assets[1].data.len).to.equal(50); - // image asset - expect(nativeRequest.assets[2].id).to.equal(3); - expect(nativeRequest.assets[2].required).to.equal(0); - expect(nativeRequest.assets[2].title).to.be.undefined; - expect(nativeRequest.assets[2].img).to.not.equal(null); - expect(nativeRequest.assets[2].img.wmin).to.equal(100); - expect(nativeRequest.assets[2].img.hmin).to.equal(150); - expect(nativeRequest.assets[2].img.type).to.equal(3); - }); - - it('Verify Native response', () => { - const request = spec.buildRequests(nativeSlotConfig); - expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); - expect(request.method).to.equal('POST'); - const ortbRequest = JSON.parse(request.data); - const nativeResponse = { - 'native': { - assets: [ - { title: { text: 'Ad Title'} }, - { data: { type: 1, value: 'Sponsored By: Brand' }}, - { img: { type: 3, url: 'http://images.cdn.brand.com/123' } } - ], - link: { url: 'http://brand.clickme.com/' }, - imptrackers: ['http://imp1.trackme.com/', 'http://imp1.contextweb.com/'] - } - }; - const ortbResponse = { - seatbid: [{ - bid: [{ - impid: ortbRequest.imp[0].id, - price: 1.25, - adm: JSON.stringify(nativeResponse) - }] - }] - }; - const bids = spec.interpretResponse(ortbResponse, request); - // verify bid - const bid = bids[0]; - expect(bid.cpm).to.equal(1.25); - expect(bid.adId).to.equal('bid12345'); - expect(bid.ad).to.be.undefined; - expect(bid.mediaType).to.equal('native'); - const nativeBid = bid['native']; - expect(nativeBid).to.not.equal(null); - expect(nativeBid.title).to.equal('Ad Title'); - expect(nativeBid.sponsoredBy).to.equal('Sponsored By: Brand'); - expect(nativeBid.image).to.equal('http://images.cdn.brand.com/123'); - expect(nativeBid.clickUrl).to.equal(encodeURIComponent('http://brand.clickme.com/')); - expect(nativeBid.impressionTrackers).to.have.lengthOf(2); - expect(nativeBid.impressionTrackers[0]).to.equal('http://imp1.trackme.com/'); - expect(nativeBid.impressionTrackers[1]).to.equal('http://imp1.contextweb.com/'); - }); - - it('Verifies bidder code', () => { - expect(spec.code).to.equal('pulseLite'); - }); - - it('Verifies bidder aliases', () => { - expect(spec.aliases).to.have.lengthOf(1); - expect(spec.aliases[0]).to.equal('pulsepointLite'); - }); - - it('Verifies supported media types', () => { - expect(spec.supportedMediaTypes).to.have.lengthOf(1); - expect(spec.supportedMediaTypes[0]).to.equal('native'); - }); - - it('Verifies if bid request valid', () => { - expect(spec.isBidRequestValid(slotConfigs[0])).to.equal(true); - expect(spec.isBidRequestValid(slotConfigs[1])).to.equal(true); - expect(spec.isBidRequestValid(nativeSlotConfig[0])).to.equal(true); - expect(spec.isBidRequestValid({})).to.equal(false); - expect(spec.isBidRequestValid({ params: {} })).to.equal(false); - expect(spec.isBidRequestValid({ params: { ct: 123 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { cp: 123 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { ct: 123, cp: 234 }})).to.equal(true); - }); - - it('Verifies sync options', () => { - expect(spec.getUserSyncs({})).to.be.undefined; - expect(spec.getUserSyncs({ iframeEnabled: false})).to.be.undefined; - 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('//bh.contextweb.com/visitormatch'); - }); - - it('Verifies image pixel sync', () => { - const options = spec.getUserSyncs({ pixelEnabled: true}); - 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'); - }); - - it('Verify app requests', () => { - const request = spec.buildRequests(appSlotConfig); - const ortbRequest = JSON.parse(request.data); - // site object - expect(ortbRequest.site).to.equal(null); - expect(ortbRequest.app).to.not.be.null; - expect(ortbRequest.app.publisher).to.not.equal(null); - expect(ortbRequest.app.publisher.id).to.equal('p10000'); - expect(ortbRequest.app.bundle).to.equal('com.pulsepoint.apps'); - expect(ortbRequest.app.storeurl).to.equal('http://pulsepoint.com/apps'); - expect(ortbRequest.app.domain).to.equal('pulsepoint.com'); - }); -}); diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index 05748d85845..6fd5c3abdf9 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -1,231 +1,216 @@ -import {expect} from 'chai'; -import Adapter from '../../../modules/quantcastBidAdapter'; -import * as ajax from 'src/ajax'; -import bidManager from '../../../src/bidmanager'; -import adLoader from '../../../src/adloader'; - -describe('quantcast adapter', () => { - let bidsRequestedOriginal; - let adapter; - let sandbox; - let ajaxStub; - - const bidderRequest = { - bidderCode: 'quantcast', - requestId: '595ffa73-d78a-46c9-b18e-f99548a5be6b', - bidderRequestId: '1cc026909c24c8', - bids: [ - { - bidId: '2f7b179d443f14', - bidder: 'quantcast', - placementCode: 'div-gpt-ad-1438287399331-0', - sizes: [[300, 250], [300, 600]], - params: { - publisherId: 'test-publisher', - battr: [1, 2], - } - } - ] - }; +import * as utils from 'src/utils'; +import { expect } from 'chai'; +import { + QUANTCAST_CALLBACK_URL_TEST, + QUANTCAST_CALLBACK_URL, + QUANTCAST_NET_REVENUE, + QUANTCAST_TTL, + spec as qcSpec +} from '../../../modules/quantcastBidAdapter'; +import { newBidder } from '../../../src/adapters/bidderFactory'; + +describe('Quantcast adapter', () => { + const quantcastAdapter = newBidder(qcSpec); + let bidRequest; beforeEach(() => { - bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; - $$PREBID_GLOBAL$$._bidsRequested = []; - - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - ajaxStub = sandbox.stub(ajax, 'ajax'); + bidRequest = { + bidder: 'quantcast', + bidId: '2f7b179d443f14', + auctionId: '595ffa73-d78a-46c9-b18e-f99548a5be6b', + bidderRequestId: '1cc026909c24c8', + placementCode: 'div-gpt-ad-1438287399331-0', + params: { + publisherId: 'test-publisher', // REQUIRED - Publisher ID provided by Quantcast + battr: [1, 2] // OPTIONAL - Array of blocked creative attributes as per OpenRTB Spec List 5.3 + }, + sizes: [[300, 250]] + }; }); - afterEach(() => { - sandbox.restore(); - - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(quantcastAdapter.callBids).to.exist.and.to.be.a('function'); + }); }); - describe('sizes', () => { - let bidderRequest = { - bidderCode: 'quantcast', - requestId: '595ffa73-d78a-46c9-b18e-f99548a5be6b', - bidderRequestId: '1cc026909c24c8', - bids: [ - { - bidId: '2f7b179d443f14', - bidder: 'quantcast', - placementCode: 'div-gpt-ad-1438287399331-0', - sizes: [[300, 250], [300, 600]], - params: { - publisherId: 'test-publisher', - battr: [1, 2], - } - } - ] - }; - - it('should not call server when empty input is provided', () => { - adapter.callBids({}); - sinon.assert.notCalled(ajaxStub); + describe('`isBidRequestValid`', () => { + it('should return `false` when bid is not passed', () => { + expect(qcSpec.isBidRequestValid()).to.equal(false); }); - it('should call server once even when multiple sizes are passed', () => { - adapter.callBids(bidderRequest); - sinon.assert.calledOnce(ajaxStub); + it('should return `false` when bid `mediaType` is `video`', () => { + const bidRequest = { mediaType: 'video' }; - expect(ajaxStub.firstCall.args[0]).to.eql(adapter.QUANTCAST_CALLBACK_URL); - expect(ajaxStub.firstCall.args[1]).to.exist.and.to.be.a('function'); - expect(ajaxStub.firstCall.args[2]).to.include('div-gpt-ad-1438287399331-0'); - expect(ajaxStub.firstCall.args[2]).to.include('test-publisher'); - expect(ajaxStub.firstCall.args[2]).to.include('2f7b179d443f14'); - expect(ajaxStub.firstCall.args[3]).to.eql({method: 'POST', withCredentials: true}); + expect(qcSpec.isBidRequestValid(bidRequest)).to.equal(false); }); - it('should call server once when one size is passed', () => { - bidderRequest.bids[0].sizes = [728, 90]; - adapter.callBids(bidderRequest); - sinon.assert.calledOnce(ajaxStub); + it('should return `true` when bid contains required params', () => { + const bidRequest = { mediaType: 'banner' }; - expect(ajaxStub.firstCall.args[0]).to.eql(adapter.QUANTCAST_CALLBACK_URL); - expect(ajaxStub.firstCall.args[1]).to.exist.and.to.be.a('function'); - expect(ajaxStub.firstCall.args[3]).to.eql({method: 'POST', withCredentials: true}); + expect(qcSpec.isBidRequestValid(bidRequest)).to.equal(true); }); + }); - it('should call server once when size is passed as string', () => { - bidderRequest.bids[0].sizes = '728x90'; - adapter.callBids(bidderRequest); - sinon.assert.calledOnce(ajaxStub); + describe('`buildRequests`', () => { + it('sends bid requests to Quantcast Canary Endpoint if `publisherId` is `test-publisher`', () => { + const requests = qcSpec.buildRequests([bidRequest]); + + switch (window.location.protocol) { + case 'https:': + expect(requests[0]['url']).to.equal( + `https://${QUANTCAST_CALLBACK_URL_TEST}:8443/qchb` + ); + break; + default: + expect(requests[0]['url']).to.equal( + `http://${QUANTCAST_CALLBACK_URL_TEST}:8080/qchb` + ); + break; + } + }); - expect(ajaxStub.firstCall.args[0]).to.eql(adapter.QUANTCAST_CALLBACK_URL); - expect(ajaxStub.firstCall.args[1]).to.exist.and.to.be.a('function'); - expect(ajaxStub.firstCall.args[3]).to.eql({method: 'POST', withCredentials: true}); + it('sends bid requests to Quantcast Global Endpoint for regular `publisherId`', () => { + const bidRequest = { + bidder: 'quantcast', + bidId: '2f7b179d443f14', + auctionId: '595ffa73-d78a-46c9-b18e-f99548a5be6b', + bidderRequestId: '1cc026909c24c8', + placementCode: 'div-gpt-ad-1438287399331-0', + params: { + publisherId: 'regular-publisher', // REQUIRED - Publisher ID provided by Quantcast + battr: [1, 2] // OPTIONAL - Array of blocked creative attributes as per OpenRTB Spec List 5.3 + }, + sizes: [[300, 250]] + }; + const requests = qcSpec.buildRequests([bidRequest]); + + switch (window.location.protocol) { + case 'https:': + expect(requests[0]['url']).to.equal( + `https://${QUANTCAST_CALLBACK_URL}:8443/qchb` + ); + break; + default: + expect(requests[0]['url']).to.equal( + `http://${QUANTCAST_CALLBACK_URL}:8080/qchb` + ); + break; + } }); - it('should call server once when sizes are passed as a comma-separated string', () => { - bidderRequest.bids[0].sizes = '728x90,360x240'; - adapter.callBids(bidderRequest); - sinon.assert.calledOnce(ajaxStub); + it('sends bid requests to Quantcast Header Bidding Endpoints via POST', () => { + const requests = qcSpec.buildRequests([bidRequest]); - expect(ajaxStub.firstCall.args[0]).to.eql(adapter.QUANTCAST_CALLBACK_URL); - expect(ajaxStub.firstCall.args[1]).to.exist.and.to.be.a('function'); - expect(ajaxStub.firstCall.args[3]).to.eql({method: 'POST', withCredentials: true}); + expect(requests[0].method).to.equal('POST'); }); - }); - describe('multiple requests', () => { - let bidderRequest = { - bidderCode: 'quantcast', - requestId: '595ffa73-d78a-46c9-b18e-f99548a5be6b', - bidderRequestId: '1cc026909c24c8', - bids: [ - { - bidId: '2f7b179d443f14', - bidder: 'quantcast', - placementCode: 'div-gpt-ad-1438287399331-0', - sizes: [[300, 250]], - params: { - publisherId: 'test-publisher', - battr: [1, 2], - } - }, { - bidId: '2f7b179d443f15', - bidder: 'quantcast', - placementCode: 'div-gpt-ad-1438287399331-1', - sizes: [[300, 600]], - params: { - publisherId: 'test-publisher', - battr: [1, 2], - } - } - ] - }; - - it('request is fired twice for two bids', () => { - adapter.callBids(bidderRequest); - sinon.assert.calledTwice(ajaxStub); + it('sends bid requests contains all the required parameters', () => { + const referrer = utils.getTopWindowUrl(); + const loc = utils.getTopWindowLocation(); + const domain = loc.hostname; - let firstReq = JSON.parse(ajaxStub.firstCall.args[2]); - expect(firstReq.requestId).to.eql('2f7b179d443f14'); - expect(firstReq.imp[0].placementCode).to.eql('div-gpt-ad-1438287399331-0'); + const requests = qcSpec.buildRequests([bidRequest]); + const expectedBidRequest = { + publisherId: 'test-publisher', + requestId: '2f7b179d443f14', + imp: [ + { + banner: { + battr: [1, 2], + sizes: [{ width: 300, height: 250 }] + }, + placementCode: 'div-gpt-ad-1438287399331-0', + bidFloor: 1e-10 + } + ], + site: { + page: loc.href, + referrer, + domain + }, + bidId: '2f7b179d443f14' + }; - let secondReq = JSON.parse(ajaxStub.secondCall.args[2]); - expect(secondReq.requestId).to.eql('2f7b179d443f15'); - expect(secondReq.imp[0].placementCode).to.eql('div-gpt-ad-1438287399331-1'); + expect(requests[0].data).to.equal(JSON.stringify(expectedBidRequest)); }); }); - describe('handleQuantcastCB add bids to the manager', () => { - let firstBid; - let addBidReponseStub; - let bidsRequestedOriginal; - // respond - let bidderReponse = { - 'bidderCode': 'quantcast', - 'requestId': bidderRequest.requestId, - 'bids': [ + describe('`interpretResponse`', () => { + // The sample response is from https://wiki.corp.qc/display/adinf/QCX + const body = { + bidderCode: 'qcx', // Renaming it to use CamelCase since that is what is used in the Prebid.js variable name + requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', // Added this field. This is not used now but could be useful in troubleshooting later on. Specially for sites using iFrames + bids: [ { - 'statusCode': 1, - 'placementCode': bidderRequest.bids[0].bidId, - 'cpm': 4.5, - 'ad': '<!DOCTYPE html>\n\n\n<div style="height: 250; width: 300; display: table-cell; vertical-align: middle;">\n<div style="width: 300px; margin-left: auto; margin-right: auto;"> \n\n <script src="https://adserver.adtechus.com/addyn/3.0/5399.1/2394397/0/-1/QUANTCAST;size=300x250;target=_blank;alias=;kvp36=;sub1=;kvl=;kvc=;kvs=300x250;kvi=;kva=;sub2=;rdclick=http://exch.quantserve.com/r?a=;labels=_qc.clk,_click.adserver.rtb,_click.rand.;rtbip=;rtbdata2=;redirecturl2=" type="text/javascript"></script>\n\n<img src="https://exch.quantserve.com/pixel/p_12345.gif?media=ad&p=&r=&rand=&labels=_qc.imp,_imp.adserver.rtb&rtbip=&rtbdata2=" style="display: none;" border="0" height="1" width="1" alt="Quantcast"/>\n\n</div>\n</div>', - 'width': 300, - 'height': 250 + statusCode: 1, + placementCode: 'imp1', // Changing this to placementCode to be reflective + cpm: 4.5, + currency: 'USD', + ad: + '<!DOCTYPE html><div style="height: 250; width: 300; display: table-cell; vertical-align: middle;"><div style="width: 300px; margin-left: auto; margin-right: auto;"><script src="https://adserver.adtechus.com/addyn/3.0/5399.1/2394397/0/-1/QUANTCAST;size=300x250;target=_blank;alias=;kvp36=;sub1=;kvl=;kvc=;kvs=300x250;kvi=;kva=;sub2=;rdclick=http://exch.quantserve.com/r?a=;labels=_qc.clk,_click.adserver.rtb,_click.rand.;rtbip=;rtbdata2=;redirecturl2=" type="text/javascript"></script><img src="https://exch.quantserve.com/pixel/p_12345.gif?media=ad&p=&r=&rand=&labels=_qc.imp,_imp.adserver.rtb&rtbip=&rtbdata2=" style="display: none;" border="0" height="1" width="1" alt="Quantcast"/></div></div>', + creativeId: 1001, + width: 300, + height: 250 } ] }; - beforeEach(() => { - bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; - addBidReponseStub = sandbox.stub(bidManager, 'addBidResponse'); - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - }); + const response = { + body, + headers: {} + }; - afterEach(() => { - sandbox.restore(); - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; - }); + it('should return an empty array if `serverResponse` is `undefined`', () => { + const interpretedResponse = qcSpec.interpretResponse(); - it('should exist and be a function', () => { - expect($$PREBID_GLOBAL$$.handleQuantcastCB).to.exist.and.to.be.a('function'); + expect(interpretedResponse.length).to.equal(0); }); - it('should not add bid when empty text response comes', () => { - $$PREBID_GLOBAL$$.handleQuantcastCB(); - sinon.assert.notCalled(addBidReponseStub); - }); + it('should return an empty array if the parsed response does NOT include `bids`', () => { + const interpretedResponse = qcSpec.interpretResponse({}); - it('should not add bid when empty json response comes', () => { - $$PREBID_GLOBAL$$.handleQuantcastCB(JSON.stringify({})); - sinon.assert.notCalled(addBidReponseStub); + expect(interpretedResponse.length).to.equal(0); }); - it('should not add bid when malformed json response comes', () => { - $$PREBID_GLOBAL$$.handleQuantcastCB('non json text'); - sinon.assert.notCalled(addBidReponseStub); + it('should return an empty array if the parsed response has an empty `bids`', () => { + const interpretedResponse = qcSpec.interpretResponse({ bids: [] }); + + expect(interpretedResponse.length).to.equal(0); }); - it('should add a bid object for each bid', () => { - // You need the following call so that the in-memory storage of the bidRequest is carried out. Without this the callback won't work correctly. - adapter.callBids(bidderRequest); - $$PREBID_GLOBAL$$.handleQuantcastCB(JSON.stringify(bidderReponse)); - sinon.assert.calledOnce(addBidReponseStub); - expect(addBidReponseStub.firstCall.args[0]).to.eql('div-gpt-ad-1438287399331-0'); + it('should get correct bid response', () => { + const expectedResponse = { + requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', + cpm: 4.5, + width: 300, + height: 250, + ad: + '<!DOCTYPE html><div style="height: 250; width: 300; display: table-cell; vertical-align: middle;"><div style="width: 300px; margin-left: auto; margin-right: auto;"><script src="https://adserver.adtechus.com/addyn/3.0/5399.1/2394397/0/-1/QUANTCAST;size=300x250;target=_blank;alias=;kvp36=;sub1=;kvl=;kvc=;kvs=300x250;kvi=;kva=;sub2=;rdclick=http://exch.quantserve.com/r?a=;labels=_qc.clk,_click.adserver.rtb,_click.rand.;rtbip=;rtbdata2=;redirecturl2=" type="text/javascript"></script><img src="https://exch.quantserve.com/pixel/p_12345.gif?media=ad&p=&r=&rand=&labels=_qc.imp,_imp.adserver.rtb&rtbip=&rtbdata2=" style="display: none;" border="0" height="1" width="1" alt="Quantcast"/></div></div>', + ttl: QUANTCAST_TTL, + creativeId: 1001, + netRevenue: QUANTCAST_NET_REVENUE, + currency: 'USD' + }; + const interpretedResponse = qcSpec.interpretResponse(response); + + expect(interpretedResponse[0]).to.deep.equal(expectedResponse); }); - it('should return no bid even when requestId and sizes are missing', () => { - let bidderReponse = { - 'bidderCode': 'quantcast', - 'bids': [ - { - 'statusCode': 0, - 'placementCode': bidderRequest.bids[0].bidId, - } - ] + it('handles no bid response', () => { + const body = { + bidderCode: 'qcx', // Renaming it to use CamelCase since that is what is used in the Prebid.js variable name + requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', // Added this field. This is not used now but could be useful in troubleshooting later on. Specially for sites using iFrames + bids: [] + }; + const response = { + body, + headers: {} }; + const expectedResponse = []; + const interpretedResponse = qcSpec.interpretResponse(response); - // You need the following call so that the in-memory storage of the bidRequest is carried out. Without this the callback won't work correctly. - adapter.callBids(bidderRequest); - $$PREBID_GLOBAL$$.handleQuantcastCB(JSON.stringify(bidderReponse)); - // sinon.assert.calledOnce(addBidReponseStub); - // expect(addBidReponseStub.firstCall.args[0]).to.eql("div-gpt-ad-1438287399331-0"); + expect(interpretedResponse.length).to.equal(0); }); }); }); diff --git a/test/spec/modules/quantumBidAdapter_spec.js b/test/spec/modules/quantumBidAdapter_spec.js new file mode 100644 index 00000000000..053ec98ffaa --- /dev/null +++ b/test/spec/modules/quantumBidAdapter_spec.js @@ -0,0 +1,268 @@ +import { expect } from 'chai' +import { spec } from 'modules/quantumBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' + +const ENDPOINT = '//s.sspqns.com/hb' +const REQUEST = { + 'bidder': 'quantum', + 'sizes': [[300, 250]], + 'renderMode': 'banner', + 'params': { + placementId: 21546 + } +} + +const NATIVE_REQUEST = { + 'bidder': 'quantum', + 'mediaType': 'native', + 'sizes': [[0, 0]], + 'params': { + placementId: 21546 + } +} + +const serverResponse = { + 'price': 0.3, + 'debug': [ + '' + ], + 'is_fallback': false, + 'nurl': 'http://s.sspqns.com/imp/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4/', + 'native': { + 'link': { + 'url': 'http://s.sspqns.com/click/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4///', + 'clicktrackers': ['https://elasticad.net'] + }, + 'assets': [ + { + 'id': 1, + 'title': { + 'text': 'ad.SSP.1x1' + }, + 'required': 1 + }, + { + 'id': 2, + 'img': { + 'w': 15, + 'h': 15, + 'url': 'http://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-15x15' + } + }, + { + 'id': 3, + 'data': { + 'value': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.' + }, + 'required': 1 + }, + { + 'id': 4, + 'img': { + 'w': 500, + 'h': 500, + 'url': 'http://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-500x500' + } + }, + { + 'id': 6, + 'video': { + 'vasttag': 'http://elasticad.net/vast.xml' + } + }, + { + 'id': 2001, + 'data': { + 'value': 'http://elasticad.net' + } + }, + { + 'id': 2002, + 'data': { + 'value': 'vast' + } + }, + { + 'id': 2007, + 'data': { + 'value': 'click' + } + }, + { + 'id': 10, + 'data': { + 'value': 'ad.SSP.1x1 sponsor' + } + }, + { + 'id': 2003, + 'data': { + 'value': 'http://elasticad.net' + } + }, + { + 'id': 2004, + 'data': { + 'value': 'prism' + } + }, + { + 'id': 2005, + 'data': { + 'value': '/home' + } + }, + { + 'id': 2006, + 'data': { + 'value': 'http://elasticad.net/vast.xml' + } + }, + { + 'id': 2022, + 'data': { + 'value': 'Lorem ipsum....' + } + } + ], + 'imptrackers': [], + 'ver': '1.1' + }, + 'sync': [ + 'http://match.adsrvr.org/track/cmb/generic?ttd_pid=s6e8ued&ttd_tpi=1' + ] +} + +const nativeServerResponse = { + 'price': 0.3, + 'debug': [ + '' + ], + 'is_fallback': false, + 'nurl': 'http://s.sspqns.com/imp/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4/', + 'native': { + 'link': { + 'url': 'http://s.sspqns.com/click/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4///' + }, + 'assets': [ + { + 'id': 1, + 'title': { + 'text': 'ad.SSP.1x1' + }, + 'required': 1 + }, + { + 'id': 2, + 'img': { + 'w': 15, + 'h': 15, + 'url': 'http://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-15x15' + } + }, + { + 'id': 3, + 'data': { + 'value': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.' + }, + 'required': 1 + }, + { + 'id': 4, + 'img': { + 'w': 500, + 'h': 500, + 'url': 'http://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-500x500' + } + }, + { + 'id': 2007, + 'data': { + 'value': 'click' + } + }, + { + 'id': 10, + 'data': { + 'value': 'ad.SSP.1x1 sponsor' + } + }, + + { + 'id': 2003, + 'data': { + 'value': 'http://elasticad.net' + } + } + ], + 'imptrackers': [], + 'ver': '1.1' + }, + 'sync': [ + 'http://match.adsrvr.org/track/cmb/generic?ttd_pid=s6e8ued&ttd_tpi=1' + ] +} + +describe('quantumBidAdapter', () => { + 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(REQUEST)).to.equal(true) + }) + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, REQUEST) + delete bid.params + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', () => { + let bidRequests = [REQUEST] + + const request = spec.buildRequests(bidRequests, {}) + + it('sends bid request to ENDPOINT via GET', () => { + expect(request[0].method).to.equal('GET') + }) + }) + + describe('interpretResponse', () => { + let bidderRequest = { + bidderCode: 'bidderCode', + bids: [] + } + + it('handles native request : should get correct bid response', () => { + const result = spec.interpretResponse({body: nativeServerResponse}, NATIVE_REQUEST) + expect(result[0]).to.have.property('cpm').equal(0.3) + expect(result[0]).to.have.property('width').to.be.below(2) + expect(result[0]).to.have.property('height').to.be.below(2) + expect(result[0]).to.have.property('mediaType').equal('native') + expect(result[0]).to.have.property('native') + }) + + it('should get correct bid response', () => { + const result = spec.interpretResponse({body: serverResponse}, REQUEST) + expect(result[0]).to.have.property('cpm').equal(0.3) + expect(result[0]).to.have.property('width').equal(300) + expect(result[0]).to.have.property('height').equal(250) + expect(result[0]).to.have.property('mediaType').equal('banner') + expect(result[0]).to.have.property('ad') + }) + + it('handles nobid responses', () => { + const nobidServerResponse = {bids: []} + const nobidResult = spec.interpretResponse({body: nobidServerResponse}, bidderRequest) + // console.log(nobidResult) + expect(nobidResult.length).to.equal(0) + }) + }) +}) diff --git a/test/spec/modules/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js new file mode 100644 index 00000000000..776261c8db2 --- /dev/null +++ b/test/spec/modules/readpeakBidAdapter_spec.js @@ -0,0 +1,199 @@ +import { expect } from 'chai'; +import { spec, ENDPOINT } from 'modules/readpeakBidAdapter'; +import * as utils from 'src/utils'; + +describe('ReadPeakAdapter', () => { + let bidRequest + let serverResponse + let serverRequest + + beforeEach(() => { + bidRequest = { + bidder: 'readpeak', + nativeParams: { + title: { required: true, len: 200 }, + image: { wmin: 100 }, + sponsoredBy: { }, + body: {required: false}, + cta: {required: false}, + }, + params: { + bidfloor: 5.00, + publisherId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', + siteId: '11bc5dd5-7421-4dd8-c926-40fa653bec77' + }, + bidId: '2ffb201a808da7', + bidderRequestId: '178e34bad3658f', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + serverResponse = { + id: bidRequest.bidderRequestId, + cur: 'USD', + seatbid: [{ + bid: [{ + id: 'bidRequest.bidId', + impid: bidRequest.bidId, + price: 0.12, + cid: '12', + crid: '123', + adomain: ['readpeak.com'], + adm: { + assets: [{ + id: 1, + title: { + text: 'Title', + } + }, + { + id: 3, + data: { + type: 1, + value: 'Brand Name', + }, + }, + { + id: 4, + data: { + type: 2, + value: 'Description', + }, + }, + { + id: 2, + img: { + type: 3, + url: 'http://url.to/image', + w: 750, + h: 500, + }, + }], + link: { + url: 'http://url.to/target' + }, + imptrackers: [ + 'http://url.to/pixeltracker' + ], + } + }], + }], + } + serverRequest = { + method: 'POST', + url: 'http://localhost:60080/header/prebid', + data: JSON.stringify({ + 'id': '178e34bad3658f', + 'imp': [ + { + 'id': '2ffb201a808da7', + 'native': { + 'request': '{"assets":[{"id":1,"required":1,"title":{"len":200}},{"id":2,"required":0,"data":{"type":1,"len":50}},{"id":3,"required":0,"img":{"type":3,"wmin":100,"hmin":150}}]}', + 'ver': '1.1' + }, + 'bidfloor': 5, + 'bidfloorcur': 'USD' + } + ], + 'site': { + 'publisher': { + 'id': '11bc5dd5-7421-4dd8-c926-40fa653bec76' + }, + 'id': '11bc5dd5-7421-4dd8-c926-40fa653bec77', + 'ref': '', + 'page': 'http://localhost', + 'domain': 'localhost' + }, + 'app': null, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/61.0.3163.100 Safari/537.36', + 'language': 'en-US' + }, + 'isPrebid': true + }) + } + }); + + describe('spec.isBidRequestValid', () => { + it('should return true when the required params are passed', () => { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false when the native params are missing', () => { + bidRequest.nativeParams = undefined; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when the "publisherId" param is missing', () => { + bidRequest.params = { + bidfloor: 5.00 + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when no bid params are passed', () => { + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when a bid request is not passed', () => { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).to.equal(false); + }); + }); + + describe('spec.buildRequests', () => { + it('should create a POST request for every bid', () => { + const request = spec.buildRequests([ bidRequest ]); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT); + }); + + it('should attach request data', () => { + const request = spec.buildRequests([ bidRequest ]); + + const data = JSON.parse(request.data); + + expect(data.source.ext.prebid).to.equal('$prebid.version$'); + expect(data.id).to.equal(bidRequest.bidderRequestId) + expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + expect(data.site).to.deep.equal({ + publisher: { + id: bidRequest.params.publisherId, + domain: 'http://localhost:9876', + }, + id: bidRequest.params.siteId, + ref: window.top.document.referrer, + page: utils.getTopWindowLocation().href, + domain: utils.getTopWindowLocation().hostname, + }); + expect(data.device).to.deep.contain({ ua: navigator.userAgent }); + }); + }); + + describe('spec.interpretResponse', () => { + it('should return no bids if the response is not valid', () => { + const bidResponse = spec.interpretResponse({ body: null }, serverRequest); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid bid response', () => { + const bidResponse = spec.interpretResponse({ body: serverResponse }, serverRequest)[0]; + expect(bidResponse).to.contain({ + requestId: bidRequest.bidId, + cpm: serverResponse.seatbid[0].bid[0].price, + creativeId: serverResponse.seatbid[0].bid[0].crid, + ttl: 300, + netRevenue: true, + mediaType: 'native', + currency: serverResponse.cur + }); + + expect(bidResponse.native.title).to.equal('Title') + expect(bidResponse.native.body).to.equal('Description') + expect(bidResponse.native.image).to.deep.equal({url: 'http://url.to/image', width: 750, height: 500}) + expect(bidResponse.native.clickUrl).to.equal('http%3A%2F%2Furl.to%2Ftarget') + expect(bidResponse.native.impressionTrackers).to.contain('http://url.to/pixeltracker') + }); + }); +}); diff --git a/test/spec/modules/realvuAnalyticsAdapter_spec.js b/test/spec/modules/realvuAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..7bb43002939 --- /dev/null +++ b/test/spec/modules/realvuAnalyticsAdapter_spec.js @@ -0,0 +1,163 @@ +import { expect } from 'chai'; +import realvuAnalyticsAdapter from 'modules/realvuAnalyticsAdapter'; +import CONSTANTS from 'src/constants.json'; + +function addDiv(id) { + let dv = document.createElement('div'); + dv.id = id; + dv.style.width = '728px'; + dv.style.height = '90px'; + dv.style.display = 'block'; + document.body.appendChild(dv); + let f = document.createElement('iframe'); + f.width = 728; + f.height = 90; + dv.appendChild(f); + let d = null; + if (f.contentDocument) d = f.contentDocument; // DOM + else if (f.contentWindow) d = f.contentWindow.document; // IE + d.open() + d.write('<img width="728" height="90" />'); + d.close(); + return dv; +} + +describe('RealVu Analytics Adapter.', () => { + before(() => { + addDiv('ad1'); + addDiv('ad2'); + }); + after(() => { + let a1 = document.getElementById('ad1'); + document.body.removeChild(a1); + let a2 = document.getElementById('ad2'); + document.body.removeChild(a2); + }); + + it('enableAnalytics', () => { + const config = { + options: { + partnerId: '1Y', + regAllUnits: true + // unitIds: ['ad1', 'ad2'] + } + }; + let p = realvuAnalyticsAdapter.enableAnalytics(config); + expect(p).to.equal('1Y'); + }); + + it('checkIn', () => { + 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.skip('isInView returns "yes"', () => { + let inview = realvuAnalyticsAdapter.isInView('ad1'); + expect(inview).to.equal('yes'); + }); + + it('isInView return "NA"', () => { + const adUnitCode = '1234'; + let result = realvuAnalyticsAdapter.isInView(adUnitCode); + expect(result).to.equal('NA'); + }); + + it('bid response event', () => { + 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); + }); +}); + +describe('RealVu Boost.', () => { + before(() => { + addDiv('ad1'); + addDiv('ad2'); + }); + after(() => { + let a1 = document.getElementById('ad1'); + document.body.removeChild(a1); + let a2 = document.getElementById('ad2'); + document.body.removeChild(a2); + }); + + const boost = window.top1.realvu_aa; + + it('brd', () => { + let a1 = document.getElementById('ad1'); + let p = boost.brd(a1, 'Left'); + expect(typeof p).to.not.equal('undefined'); + }); + + it('addUnitById', () => { + let a1 = document.getElementById('ad1'); + let p = boost.addUnitById('1Y', 'ad1'); + expect(typeof p).to.not.equal('undefined'); + }); + + it('questA', () => { + const dv = document.getElementById('ad1'); + let q = boost.questA(dv); + expect(q).to.not.equal(null); + }); + + it('render', () => { + 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', () => { + const a = boost.ads[boost.len - 1]; + let r = boost.readPos(a); + expect(r).to.equal(true); + }); +}); diff --git a/test/spec/modules/rhythmoneBidAdapter_spec.js b/test/spec/modules/rhythmoneBidAdapter_spec.js index 5931ed77626..ad113b9021a 100644 --- a/test/spec/modules/rhythmoneBidAdapter_spec.js +++ b/test/spec/modules/rhythmoneBidAdapter_spec.js @@ -1,69 +1,45 @@ -var Adapter = require('../../../modules/rhythmoneBidAdapter'); +import {spec} from '../../../modules/rhythmoneBidAdapter'; var assert = require('assert'); describe('rhythmone adapter tests', function () { - describe('rhythmoneResponse', function () { - var fakeResponse = { - 'id': '1fe94c2e-4b31-4e09-b074-ba90fe7ce92d', - 'seatbid': [ - { - 'bid': [ - { - 'id': 'ff8b09b1-5264-52be-4b7b-0156526452bf', - 'impid': 'div-gpt-ad-1438287399331-0', - 'price': 1.0, - 'adid': '35858', - 'adm': "<a href=\"http://tag.1rx.io/rmp/34887/0/ch?ajkey=V12FF640238J-573H14H141407CFD01K3585…0H16X12iamspartacus2EW3comH16X12iamspartacus2EW3comG0QG0Q919191I72600005A\" target=\"_blank\"><img src=\"http://img.1rx.io/banners/media/0/14/0/78/1464186216852_1R-300x250_border.png\" height=\"250\" width=\"300\" border=\"0\" alt=\"\"></a><script src=\"http://tag.1rx.io/rmp/34887/0/impr?ajkey=V12FF640238J-573H14H141407CFD01K35…3comH16X12iamspartacus2EW3comG0QG0Q919191I72600005A&obid=${AUCTION_PRICE}\" type=\"text/javascript\"></script><script type=\"text/javascript\">\n setTimeout(function() {\n var iframe = document.createElement('iframe');\n var xpr9190 = \"http\";\n if (window.location.protocol == 'https:') xpr9190 += \"s\";\n iframe.id = iframe.tagName+\"_\"+(Math.random() * 1000000000);\n iframe.style.display = \"block\";\n iframe.style.width = \"0px\";\n iframe.style.height = \"0px\";\n iframe.src = xpr9190 + \"://sync.1rx.io/usersync2/rmp\";\n if ((document.body === undefined) || (document.body == null )) { \n var iframeHtml = iframe.outerHTML || (function(n){ /**/\n var div = document.createElement('div');\n div.appendChild( n.cloneNode(true) );\n return div.innerHTML;\n })(iframe);\n document.write(iframeHtml);\n } else {\n /**/\n document.body.appendChild(iframe);\n }\n }, (Math.floor(Math.random() * 5)+1));\n</script>", - 'adomain': ['www.rhythmone.com'], - 'cid': '35857', - 'cat': [], - 'h': 250, - 'w': 300 - } - ], - 'seat': '14', - 'group': 0 - } - ], - 'bidid': 'ff8b09b1-5264-52be-4b7b-0156526452bf' - }; + describe('auditBeacon', function() { + var z = spec; + var beaconURL = z.getUserSyncs({pixelEnabled: true})[0]; + + it('should contain the correct path', function() { + var u = '//hbevents.1rx.io/audit?' + assert.equal(beaconURL.url.substring(0, u.length), u); + }); + }); - var endEvent = function() {}, - wonEvent = function() {}; ; + describe('rhythmoneResponse', function () { + var z = spec; - var z = new Adapter( - { - addBidResponse: function(placementcode, adResponse) { - it('should echo placementcode div-gpt-ad-1438287399331-0', function() { - assert.equal(placementcode, 'div-gpt-ad-1438287399331-0'); - }); - it('should have the expected ad response', function() { - assert.equal((adResponse.ad === undefined || adResponse.ad.length > 0), true); - assert.equal(adResponse.width, 300); - assert.equal(adResponse.height, 250); - assert.equal(adResponse.cpm, 1); - assert.equal(adResponse.bidderCode, 'rhythmone'); - }); - } - }, - { - 'navigator': {}, - '$$PREBID_GLOBAL$$': { - 'onEvent': function(e, f) { - if (e.toLowerCase() === 'auctionend') endEvent = f; - if (e.toLowerCase() === 'bidwon') wonEvent = f; + var rmpRequest = z.buildRequests( + [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'xyz', + 'keywords': '', + 'categories': [], + 'trace': true, + 'method': 'get', + 'endpoint': 'http://fakedomain.com' }, - 'getBidResponses': function() { return {'div-gpt-ad-1438287399331-0': {'bids': [{cpm: 1, bidderCode: 'rhythmone'}, {cpm: 2, bidderCode: 'rhythmone'}]}}; }, - 'version': 'v0.20.0-pre' + 'mediaType': 'video', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]] } - }, - function(url, callback) { - callback(JSON.stringify(fakeResponse), {status: 200, responseText: JSON.stringify(fakeResponse)}); - }); + ] + ); + + it('should have one request to RMP', function() { + assert.equal(rmpRequest.length, 1); + }); - z.callBids({ - 'bidderCode': 'rhythmone', - 'bids': [ + var mangoRequest = z.buildRequests( + [ { 'bidder': 'rhythmone', 'params': { @@ -72,19 +48,34 @@ describe('rhythmone adapter tests', function () { 'categories': [], 'trace': true, 'method': 'get', + 'api': 'mango', 'endpoint': 'http://fakedomain.com' }, - 'mediaType': 'video', - 'placementCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', 'sizes': [[300, 250]] } ] + ); + + it('should have one request to Mango', function() { + assert.equal(mangoRequest.length, 1); + }); + + var bids = z.interpretResponse({ + body: [ + { + 'impid': 'div-gpt-ad-1438287399331-0', + 'w': 300, + 'h': 250, + 'adm': '<div style=\"width:298px;height:248px; background-image: url(\'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAdQAAABuCAYAAACeAeLLAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAABfLSURBVHhe7Z3Lrh3HdYb5BtIoUwqI5xKSBxDzBOQTRHyABOTcBnTG8YAcxiNxFs/EiT0VB/FYCAQYMCCAiAEBAgRIok1LlqxkB9/GKaZOndVdq2592/8H/BCos7uqunfv+nutuvSd0w759NNPT8+ePTtdXV2dHj58eLp3797pnXfeOb3//vvXn7jNJ598crpz5875s+jx48fn458/f356+fLl9aeEEEKIOnZhqJge5ocRYopT8hjqlN5+++1z+dSDYQshhBAlbNJQv/nmm3ME+uDBA9P8ptRiqKkwWKJfzJz2CCGEEHNsylAxr1ITjdXTUFNhrpQhhBBCWKxuqIxfkmZlDNQyshKNNNQg2vn06VNFrUIIIW6wmqFiSER9lmnVaglDDSIlzIOAjFUIIQQsbqgYEEaEIVlG1aIlDTVIxiqEEAIWNVRSpSOMNGgNQw0iFfzxxx9f1yaEEOLSWMRQWYaSW/LSQ2saahCTqrSuVQghLo/hhko61DKe3sJMHz16dF3rbTC5+/fvnz939+5ds4xeIgonGhdCCHE5DDNUDOy9994zDadFmOEHH3xwevLkyTnqbIHI+aOPPjqX9+6775r1tYhoVWOrQghxGQwxVIyu51gpkSUGOjqVivlhsNRntaNGjK1q5yUhhDg+3Q21V4qXiBFzWyvCC+baK3KlLCGEEMelq6H2WFdK+rU1ldsbIkzaZbW3RGzIL4QQ4ph0MVSiuZYtAxGGtfXZsbSv1Vh56BBCCHE8mg0VM22ZfMSs260baQrtpd3W+XikyUpCCHE8mgy1xUzfeuut3W+EwLgo52GdX06YqhBCiOPQZKi1Zsos2qNEaJxH7axgpX+FEOI4VBtqzQQkormjznblvKxzzkmmKoQQx6DKUGvMlA0Zjr4ek/Or2YXpww8/vC5BCCHEXik21JpIjLWclzIJh/OsWbuqjfWFEGLfFBkqEVjpDkhHGi/1UjOuynXd22xnIYQQ/4/bUDEJttGzzGBKrNm8ZErXrDLJ69IePoQQ4ii4DbV044ZLN9NAqanOvTFHCCHEdnEZKuN7Vuc/pbn3kl4ipWOqW9t6UQghRJ6soZamei9pApIXrkeJqXK9hRBC7IusoZKCtDp9S6wz7TGx5rPvfzr97nVe//3D/1wfcRPa8OLFi6yWXMZDm0p2VdJSGiGE2Bezhkrq0ersp9QrVXn/8z+f/u6/vs3q3778/vqIm2BGVvumxGQg3gQz2mBLU+ea9SuEEPth1lDv3btndvSWekZUSxtqLNKtz549uy6pPyVt08QuIYTYD5OGWhKdMj7YkzUNNQhjHbXZQsl46tF3lxJC9CcMi/321d9Ov/zyr2/Ev8Pfvv3pf68/LXoxaagl0WnvTn8Lhho04lVrXC+rLkt8D0tjtaNVnEfQ06dPq+8Zz4Nez2yJuEnutYWa4b8OGOUvvvj+9ODz12ZfOaV/+sOfT//6x+9Ov/76RxlsB0xDLRnrG9F5/eyzV+aXn2oJQ0VEq70fGkrauPQyGqsNI1STXpehrosMdTtggL/66ofTP/7+T2b/WCr6Xcx1arKnyGMaqjc6ZSP43tEbX6b1ZVtaylARWwP2NFWum3fW79JjqVYbRqpkhygZ6rrIULfBf3z9gzvwqBHGqoi1nFuGysxS64diacSr2DBJ6wu2tKShot6m+uTJE7MeS0vO+LXqHy2vqcpQ10WGui6Y3Acv/2L2h71F5Pufr/92XbPwcMtQvVvlEZ32hui05KlraUNFpCl7RuXe170taRJW/UuI8eocMtR1kaGuB2bKmKfVF46UTNXPDUPFKLxvk+kdnWKmpTfLGoaKPB2/F+/r8DDypbDqX0q5mdUyVB9syBJPBIvV8tuVoa7DWmaKCHLYbEfkuWGo3s69JTrlxgjTtoOYzl0zHjBlqJwHP+w51bwIPFbPJTXesdRRy3hSrLqXEh3+HDJUH9Z1CWq5Pvx2rDKD+LvoT+ns3aB7f/jTedVELOtzOdE/a0w1zw1D9b7Ds+UHSfrA+sJqNGWoXhiXxHxrzLVnxOiNqJeanGTVnYq20O6ciJRKXw4wN14sQ/VhXZeglusjQ12e37z60ez/LP39Z9+efv7Fd9k0LRHnv3/117PhWuVYau1vL4Ebhmr9QCy1TJDZkqHGMEGoZK9d1JI6i/FOBCMdvwRW3akwthL4vPf6znX4MtQ8uWskQ90X3mUx//zydVUUibFa5aUiStWSmnneGKp37SlRbAtbNVRgBm+JqTIztRfe7ECpkdVg1Zuqph1cX6usVHP3mAw1jwz1OHij03/541+uj6iDZThWuakUpc7zxlC9s3tbo7ItGyqUmmqvZTTe8eslXkBu1Zuq1tg999lcOl2Gmie3HEuGuh8wSqvvi0Wat8f4pmd8lYlRYpo3hkonZv1AUrUuGdm6oULJ+lA+2wOuq1V+qp5R8RRWvalqDdWbCZlChpqH87euS5AMdT94Jmv26ge90bAmJ01z7rm8Y3it6V7Yg6GCd6JSj2sS8E7eGY1VZ6paQ/Xea1PUGCoPK7z/lj2Er66uzmI2Mcufwr/5+1HIDR8sbahkcZ4/f/7mWj98+PB8/cO/+VvLvAwvfMdsdRnq5ZWNtIP/8m/uj5L7INxXoTxEeZxfTXkpTByy+r1UPZe0WOWnog9vhTanG/czk5kdmsK/WQEycsyWsq02BIX/x/aOtMXDuefyRg09orG9GKo3Su0ZMeYii6BaM/Ni1ZmqpQ1Weamm8BoqHRkdJd+P9Zkp0RnmOndP2rp2iZPnvosNMWdwtZqKND2GyvXDTDAX6zNT4vM9X52IUQfTtOqbEpP/uA+s4ZxQpjejh0J5pQ8N3r6yJ55Zv0SypdRu3o9IM/favJ9rimnXLNNE7FJFW6Y491xLduR7MVRPxx3UC2+dLRGGB6vOVFs21B4iwpjC04baJU6eB4C4Y96aofYQBlgbsWKCmJd3g5qcME/K5H4oMVFLtIkHDS+eFCzjpz3xjKN6+11MtMW8UrVshUhEXGPmU5pqy7nn8v5IesBTBg2Zk3dt1EhDBesaWOqFdxy1Z5rZwqozVa2hes9xiqUMFdExT5EbEqhZ4uS5NqnRHdFQEdeP6+GFyLY0G7GW5u6rGPo3q9+LhQH2pNVQ6d9Jk/Z6A44lZiSX4J3BXKO0Leeey/M0N/UDG4F3N4+Rhurt+FFPPOOooycmWXWmqjVUryFOsaShoqlsgCc1W5r29cz0TmfZH9VQkfc+b40c15Any7Q3Qx39BpxYXlMdaaZBBIGBc89lfeGplliuEdiCoZZ03D3JTSgJGolVX6paQ+U+ssqLNbe15dKGiqz0o+eBqzTt6/nu06jtyIaKPPM2rOO2Lk8EvjdD9bS3lzDu3Jgqf1/C4InGA3cYH7C+8FSjx+1itmCo3klJc51/Dd7x7JFY9aWqMVQ6EE80MdWZQ62hEvlTLirdCnHKGHOTk0rSvrUGPcrgpr6DmvpY1x2ufenxnmtoHTen+F6o2XY0Ve255YKUSzDUf/j9q3OdqGQbRJTbzIItGK3jUjEOzS5TtD8Wx3vbFCLmO94OqjYiqWELhupNI/Uez/Ru8DDy+7DqS1VTP2NHVlmp5h7evPcrnTgGRNrVigT4f96HF+4FC09bvGlfz/dulcUDMe2IZR0bq+Xh2Gsa/C44p6nJRfzNa2ac0xzWManCvWBBG72ZIRTuLWsWMHBuns1hpu6rAP2b1e/F2qOhYoRMuLIiTP4fWyFictaxseLI0MITnVJXDk/aGEOGO1vowFPWNlRvdIo8KakSPB0iGvl9WPWl8tZPZ8WEkZJxrqmOCjzXx4rkpvDe/1NtypmCty25Dr0kE2IdH2ukoRL9eWfo8lDjMZ5ce61jYnnP12OqnL/n/Ly/47myjmio8XjjHHzOOj7V1Bpcz/FEoF5y54Z5w9jcYSVrGiodp2eSVpC389gT1nkupZxxeDqqUsPwRF1TDxDUZX0+yJv2zd1zJXMYrONjjTRU/l6C54Em91BiHRPLe778lq3jY5Wcn8eg5x5ML9lQgajPKiPW1JpYT1tKNo3gs1YZsTB3GWpEqZnyNH5ErHNdSnSwc4wwVE9GYqpdnk44l/bl79Zxseai9hTr+FhbMlTP2HGuTOuYWCXn60lDe/HcV3P3xqUbqifVOtWWnBnXrN9lvNcqK4hzk6Few4Jr64afU67z3yvWuS4hzwPKCENtLTMXieQiLP5uHRdU+uBmlRFrS4YKVjmxljTU3PkhL6331aUbqidtO9WW3HnUXLdcmYzHXrShEl1gpCXje0ElY1p7wzrf0WIszZo8lLJFQ81FmLm0by4rUjpOb5URa2uGygODVVaQDHVaNcYwh6fvXcpQPWnWqbawXaH1+aARhkpbDmmoRI5sXzanktSupVwab89Y5ztSdKgeM4UtGirkUoVT94sn3eu9NgGrjFhbM9TWMq1jYslQ/WzJUMEqI9ZUW6zPxqq5brk0Mm05pKFyk1o3by+R4jsy1jmPEFFpadp8q4aa27BiKu2bO67mXrPKiVV6fWJkqDLUqX7X096tGOoI0RYZaqGIREojhr1hnXdPkR0omWQTs1VDzU1Omkr75oYbajIhVjmxSq9PjAxVhjrV73rae3hD5Qu1vuhUS7JVQyWiqjWCEqy6U7V0ijms+lLRWVhwfazPp5o6PsdWDRVynXFqjrlrxf1Wg1VWrJZ7p9X8LFrLtI6JVXK+ubYgLzLU2zq8oXo3MajtAGvYoqEuZaZeQ2rpFHNY9aWaux88nRLj2DVs2VBJX1vHBqVp39o0cQ6rrFgt906r+Vm0lmkdE6vkfD33rhcZ6m0d3lA9Xzqa60B7szVDZdLMEmYKW/g+rPpSzdXvPYeadKan7FLD6FkmD17W8ShN++bSvbX3nFVWrNLrEyNDXc5QPeswc9vvlXIUQ/VsXdhbRYbae4u9ObZkqEwKWXLMdAsZA6u+VLn6PR1Tbi9Ti57mF+hZZi7qDA8RuUxEy7Isq7xYpdcnptX8LFrLtI6JVXK+nvvWS+t9hflY/V6qnnjeY9qyO9FShprzEDa9py09xTKfO5iF9UWnKtn6rJWtGOqS5xzIdchBI7c8tOpLlTNUT2eCjjLLN5CbnBTSuLkHp5YHWKu8WKXXJ0aGuj1DLdlCbw42prfKT0W7LPZkqPx9BOe7w/qiU9X8UGrZiqGSolt6Rq/nB41GYtWXKmeo4DmX0mu8dUOFuY0KQtqXl2dbfw9qeWCyyotVen1ict9pTT/RWqZ1TKyS8/Xcs15a7yuvwYVXh7XiSTGjKQPfkqHm1oyy8cMIzneH5yZCS7EVQ0UPHjy4LnUZrDakqum0SrDqTOUxVE+Hgko6vD0Yam5yUi46bf1+rTJjlV6fmFxfUdP21jKtY2KVnG+uLchLj/vKs0F8r3FUT7qXVOkUWzJUT1tGcL47vGnGpSbmtBoqT/fczFPy/Ghi1UyeqcE7w3d0KtqqMxXX0UNuWzlUEqVSr1VGrLUNlXPJTU6y/n9QaRo8xSozlgx1Gk/f4KXHfeWNGqf6Qi8eA0Jz7w/1lLGUoVKP9flYU2PBLZzvjtwTdVDrD91Lq6HmwHDnOrxUS6V+t/I9WHWm8hqq95y8S0T2YKjA+Vjl5MR92XqvWeXGankgk6Eua6ikfb0zVn/5Zf5l2RYcZ5WXinbMjdduyVA96XJedN6b893hjYyW2nJvtKGCdzZt0BKp39xbS4JGZwqsOlN5DRU8r8RCnnHDvRiq9zeVqnbtaYxVbqya2dUBGeqyhgoeowoibfurr344G8oc/P3XX//oSvMG5frbLRkqeHykd5T65u7wRmxLRGpLGCp4fjyxRqZ+ua5Wnan4nkZj1ZuqxFC9Uapns4e9GCp40t2petxjnnofPnxY9VtuNT+L1jKtY2Lt3VAh9y5OSz/77NXpweevb6nERIOoP8fWDBWztI5J9Ysvvj+/HNwLn/3d65/MjMCbu8ObolpiPHEpQ91S6tdrOktkCax6U5UYKnij1Fy5ezLU0ixIy9rTGO+cCBS/gQmF2cdTyFDXMVQ68TU2K0DU6zGcrRkqlD6IWA8gQTygpJ9PZ1i/uTt6j3W1sJShwlZSv94HmtHjp2DVm6rUUHtFqXsyVG/WIajXZDPP+eQ0lX6Xoa5jqLCGqXrNFLZoqNRnHddLzMKOeXN3lPz4R0VpgSUNFTw/oli9o3Q6L6seS6OvPVj1pio1VPBGqXMPDXsyVCiZnOQZQ/biHY+f0tT322p+Fq1lWsfEOoqhAuZWk/6tEUtkvGYKWzRU8LSrRfF49Y27w3MzodrOxcvShrp26pfradWTirGxJbDqTlVjqN5swNykmb0Zqqds1Pu75f6sGcMN4ruyaDU/i9YyrWNilXx3nj7Qy6j7ig785198Z/aJvVTTt27VUGGkqcZp3xt3hzct1zJL0MPShgprpn5zm6QHTXVyvbHqTlVjqHTy3geXqXPdm6GCJzIf8d1yvT0GYWnqfFvNz6K1TOuYWEcz1ADRI0s/eqWBKYfyarcy3LKhApOURkT38a5LN+6Okg5v5FjeGoYKnh9TrB6pX+9DDOoZFc9h1Z2qxlCBDsQqL9VUFmCPhup5WBv53XKPlUarUybWan4WrWVax8Q6qqEGiFjZcIHxvFLDwEQ5juNzS21ybN1QA0SUpLOt8jzCn8gQUE6aEr91d3jHfEZGqWsZ6hqpX290usRkMDGG3Bj5Uuu7aQedPJ24JYyfvy+1I5oYAxEmxoVJ0kda4u+9NtXfM7nrhDBOPud54LhlqCUL0kdFqWsZKniiiVgtqd+S6JSOTuyTnKEusRRNCDEeM3/hTQ8RXY1IVa1pqOBJ+8Sq6RC5bt7odKnJSGIMLAWyvlfUa+2pEGJ9TEMtiZxIFfVmbUNdIvXLdbPKsjQqEyDGk/stjZiMJIRYh8kRdu+aQdR7zGVtQ4WRqd+StLoimP1Cmp6HLet7RTy0jZyMJIRYlklDJY1pdQKWcrvblLIFQ4VRqd/cy6Vjaex0f2CSV1dX5vcZS9GpEMdi0lChxFB6pn6ZisysqpxGz1KjY8TQvPJE6iWp3txyAbEu4ftmiODFixenZ8+enTedn4tKgzQuLsTxmDVUTMLqDKbE58U0up7HITdzd06kensPkwgh1mfWUKFkL9Ie6zKPCh2wJ3IJ6rVRuhhDycS9VFomI8QxyRoqBlky45XxQZnqTbgeJeOmmqyyfUoeNGNpxrYQxyVrqFD6NN57ktLeKTFTpAhm+5R+p4yZKs0rxLFxGSqUvg6KyRnidL4O1vWZ0lLb0Il6yB5Y350llj0pKhXiMnAbKp1IydpUdOmmWmqmXF+lercP4+HM1ibty0zsVIx/syRGEakQl4XbUIEOwjKCOWEql2YSnG+pmSJ1wEIIsV+KDBVKdxBClzRRifMsHV9DSgsKIcS+KTZUqJnhyEbwR4/AOD/vhvex9Go2IYTYP1WGCjWmyjrMo0ZinFfJOtMgmakQQhyDakMltel9zVsqNpI/SgqY8+B8rPPMSdvPCSHEcag2VGgxVaK5va+3pP01USniuh3loUIIIUSjoUKLqSI2gWAZwp6gvbVRKZKZCiHE8Wg2VGg1VcQyk60bK+2rWQ4TizFTmakQQhyPLoYaqJmolArD2tpsYNrTaqRIE5CEEOK4dDVUKHnf55xIBfN+yTWhftphta9UPd8XK4QQYnt0N1Rgsk7JG2pyYrwScxudKqV86mkZH03FddB7TYUQ4vgMMVRgvLF1XNUSuxA9fvz4bHytY66kcimH8mp2N8pJk4+EEOJyGGaogV4p4DlhhqRm2RZxDiJPPlezm1GJiEqV4hVCiMtiuKECkeCIaDUVb/qYwzqmt2jD1mcrCyGE6M8ihhogguw5tppqTUPlvLTBvRBCXC6LGiowpkg6dISxrmGoIb2rsVIhhLhsFjfUAGnRHutWYy1pqDJSIYQQMasZagBjxZju3r1rGleJljBU2knqWkYqhBAiZnVDjWH9KqZoGZlHIw2VaFrrSYUQQkyxKUMNEP0xwef+/fumuU2pp6GS0sVEaYeiUSGEEDk2aagxmBmRK2nhXPTaYqgYKMdTjyJRIYQQpWzeUC1Y10rkiPkRRWKEjG16DJXPoEePHp2Px6y1blQIIUQbp9P/AXD2zRKU3utuAAAAAElFTkSuQmCC\'); background-color: white; background-repeat: no-repeat; background-size:contain; background-position: center center; border: 1px solid black;\"><div style=\'padding: 5px;\'>My ad4 with cpm of a4ab3485f434f74f</div></div>', + 'price': 1, + 'crid': 'cr-cfy24' + } + ] }); - endEvent(); - wonEvent({ - bidderCode: 'rhythmone', - adUnitCode: 'div-gpt-ad-1438287399331-0' + it('should register one bid', function() { + assert.equal(bids.length, 1); }); }); }); diff --git a/test/spec/modules/rockyouBidAdapter_spec.js b/test/spec/modules/rockyouBidAdapter_spec.js new file mode 100644 index 00000000000..f929b50d581 --- /dev/null +++ b/test/spec/modules/rockyouBidAdapter_spec.js @@ -0,0 +1,494 @@ +import { expect } from 'chai'; +import { spec, internals } from 'modules/rockyouBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('RockYouAdapter', () => { + const adapter = newBidder(spec); + + describe('bid validator', () => { + it('rejects a bid that is missing the placementId', () => { + let testBid = {}; + expect(spec.isBidRequestValid(testBid)).to.be.false; + }); + + it('accepts a bid with all the expected parameters', () => { + let testBid = { + params: { + placementId: 'f39ba81609' + } + }; + + expect(spec.isBidRequestValid(testBid)).to.be.true; + }); + }); + + describe('request builder', () => { + // Taken from the docs, so used as much as is valid + const sampleBidRequest = { + 'bidder': 'tests', + 'bidId': '51ef8751f9aead', + 'params': { + 'cId': '59ac1da80784890004047d89', + 'placementId': 'ZZZPLACEMENTZZZ' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [[999, 888]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'mediaTypes': { + banner: { + 'sizes': [[320, 50], [300, 250], [300, 600]] + } + } + }; + + it('successfully generates a URL', () => { + const placementId = 'ZZZPLACEMENTZZZ'; + + let bidRequests = [ + { + 'params': { + 'placementId': placementId + } + } + ]; + + let results = spec.buildRequests(bidRequests, { + bidderRequestId: 'sample' + }); + let result = results.pop(); + + expect(result.url).to.not.be.undefined; + expect(result.url).to.not.be.null; + + expect(result.url).to.include('/servlet/rotator/' + placementId + '/0/vo?z=') + }); + + it('uses the bidId id as the openRtb request ID', () => { + const bidId = '51ef8751f9aead'; + + let bidRequests = [ + sampleBidRequest + ]; + + let results = spec.buildRequests(bidRequests, { + bidderRequestId: 'sample' + }); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + expect(payload.id).to.equal(bidId); + }); + + it('generates the device payload as expected', () => { + let bidRequests = [ + sampleBidRequest + ]; + + let results = spec.buildRequests(bidRequests, { + bidderRequestId: 'sample' + }); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + let userData = payload.user; + + expect(userData).to.not.be.null; + }); + + it('generates multiple requests with single imp bodies', () => { + const SECOND_PLACEMENT_ID = 'YYYPLACEMENTIDYYY'; + let firstBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + let secondBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + secondBidRequest.params.placementId = SECOND_PLACEMENT_ID; + + let bidRequests = [ + firstBidRequest, + secondBidRequest + ]; + + let results = spec.buildRequests(bidRequests, { + bidderRequestId: 'sample' + }); + + expect(results instanceof Array).to.be.true; + expect(results.length).to.equal(2); + + let firstRequest = results[0]; + + // Double encoded JSON + let firstPayload = JSON.parse(firstRequest.data); + + expect(firstPayload).to.not.be.null; + expect(firstPayload.imp).to.not.be.null; + expect(firstPayload.imp.length).to.equal(1); + + expect(firstRequest.url).to.not.be.null; + expect(firstRequest.url.indexOf('ZZZPLACEMENTZZZ')).to.be.gt(0); + + let secondRequest = results[1]; + + // Double encoded JSON + let secondPayload = JSON.parse(secondRequest.data); + + expect(secondPayload).to.not.be.null; + expect(secondPayload.imp).to.not.be.null; + expect(secondPayload.imp.length).to.equal(1); + + expect(secondRequest.url).to.not.be.null; + expect(secondRequest.url.indexOf(SECOND_PLACEMENT_ID)).to.be.gt(0); + }); + + it('generates a banner request as expected', () => { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + let results = spec.buildRequests([localBidRequest], { + bidderRequestId: 'sample' + }); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.banner).to.not.be.null; + + let bannerData = firstImp.banner; + + expect(bannerData.w).to.equal(320); + expect(bannerData.h).to.equal(50); + }); + + it('generates a banner request using a singular adSize instead of an array', () => { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + localBidRequest.sizes = [320, 50]; + localBidRequest.mediaTypes = { banner: {} }; + + let results = spec.buildRequests([localBidRequest], { + bidderRequestId: 'sample' + }); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.banner).to.not.be.null; + + let bannerData = firstImp.banner; + + expect(bannerData.w).to.equal(320); + expect(bannerData.h).to.equal(50); + }); + + it('fails gracefully on an invalid size', () => { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + localBidRequest.sizes = ['x', 'w']; + + localBidRequest.mediaTypes = { banner: { sizes: ['y', 'z'] } }; + + let results = spec.buildRequests([localBidRequest], { + bidderRequestId: 'sample' + }); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.banner).to.not.be.null; + + let bannerData = firstImp.banner; + + expect(bannerData.w).to.equal(null); + expect(bannerData.h).to.equal(null); + }); + + it('generates a video request as expected', () => { + // clone the sample for stability + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + localBidRequest.mediaTypes = { video: { + playerSize: [326, 56] + } }; + + let results = spec.buildRequests([localBidRequest], { + bidderRequestId: 'sample' + }); + let result = results.pop(); + + // Double encoded JSON + let payload = JSON.parse(result.data); + + expect(payload).to.not.be.null; + + let imps = payload.imp; + + let firstImp = imps[0]; + + expect(firstImp.video).to.not.be.null; + + let videoData = firstImp.video; + + expect(videoData.w).to.equal(326); + expect(videoData.h).to.equal(56); + }); + + it('propagates the mediaTypes object in the built request', () => { + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + localBidRequest.mediaTypes = { video: {} }; + + let results = spec.buildRequests([localBidRequest], { + bidderRequestId: 'sample' + }); + let result = results.pop(); + + let mediaTypes = result.mediaTypes; + + expect(mediaTypes).to.not.be.null; + expect(mediaTypes).to.not.be.undefined; + expect(mediaTypes.video).to.not.be.null; + expect(mediaTypes.video).to.not.be.undefined; + }); + }); + + describe('response interpreter', () => { + it('returns an empty array when no bids present', () => { + // an empty JSON body indicates no ad was found + + let result = spec.interpretResponse({ body: '' }, {}) + + expect(result).to.eql([]); + }); + + it('gracefully fails when a non-JSON body is present', () => { + let result = spec.interpretResponse({ body: 'THIS IS NOT <JSON/>' }, {}) + + expect(result).to.eql([]); + }); + + it('returns a valid bid response on sucessful banner request', () => { + let incomingRequestId = 'XXtestingXX'; + let responsePrice = 3.14 + + let responseCreative = '<style>\n body {\n margin:0px;\n padding:0px\n }\n #RyImgContainer {\n width:100%;\n height:100%;\n position:absolute;\n background-image: url(\"http://robertwlaschintest.rockyou.com/images/banners/adhawk3_test_728x90.png\");\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n }\n</style>\n<a href=\"http://tas-qa.rockyou.com/servlet/rotator/273/0/ch?ajkey=V1290DDF4BAJ-573J8400I2011AC181011I276I274QI219QQP0G00G0I2741274D66E000001010000G0PH101H36W8c21bab0e2DW476682DW44d8f2DW4908a2DX1263e094c09197H24X24AC181011AC18101134440BC2H44W7XU01v042DW8659e0a222DW4d04a2DW446612DW4bca22DX12cca633a0680e00G0G01FFG0H84W4http3A2F2FW5https25334125324625W52Fwww2EW9w3schools2EW3com25W62Fhtml25W72Ftryit2EW3asp25X103Ffilename25X153Dtryhtml_basic05\" target=\"_blank\">\n <div id=\"RyImgContainer\"></div>\n</a><script src=\"http://tas-qa.rockyou.com/servlet/rotator/273/0/impr?ajkey=V1290DDF4BAJ-573J8400I2011AC181011I276I274QI219QQP0G00G0I2741274D66E000001010000G0PH101H36W8c21bab0e2DW476682DW44d8f2DW4908a2DX1263e094c09197H24X24AC181011AC18101134440BC2H44W7XU01v042DW8659e0a222DW4d04a2DW446612DW4bca22DX12cca633a0680e00G0G01FFG0H84W4http3A2F2FW5https25334125324625W52Fwww2EW9w3schools2EW3com25W62Fhtml25W72Ftryit2EW3asp25X103Ffilename25X153Dtryhtml_basic05&obid=${AUCTION_PRICE}\" type=\"text/javascript\"></script>'; + + let responseCreativeId = '274'; + let responseCurrency = 'USD'; + + let responseWidth = 300; + let responseHeight = 250; + let responseTtl = 213; + + let sampleResponse = { + id: '66043f5ca44ecd8f8769093b1615b2d9', + seatbid: [ + { + bid: [ + { + id: 'c21bab0e-7668-4d8f-908a-63e094c09197', + impid: '1', + price: responsePrice, + adid: responseCreativeId, + adm: responseCreative, + adomain: [ + 'www.rockyouteststudios.com' + ], + cid: '274', + attr: [], + w: responseWidth, + h: responseHeight, + ext: { + ttl: responseTtl + } + } + ], + seat: '201', + group: 0 + } + ], + bidid: 'c21bab0e-7668-4d8f-908a-63e094c09197', + cur: responseCurrency + }; + + let sampleRequest = { + bidId: incomingRequestId, + mediaTypes: { banner: {} }, + requestId: incomingRequestId + }; + + let result = spec.interpretResponse( + { + body: sampleResponse + }, + sampleRequest + ); + + expect(result.length).to.equal(1); + + let processedBid = result[0]; + + // expect(processedBid.requestId).to.equal(incomingRequestId); + expect(processedBid.cpm).to.equal(responsePrice); + expect(processedBid.width).to.equal(responseWidth); + expect(processedBid.height).to.equal(responseHeight); + expect(processedBid.ad).to.equal(responseCreative); + expect(processedBid.ttl).to.equal(responseTtl); + expect(processedBid.creativeId).to.equal(responseCreativeId); + expect(processedBid.netRevenue).to.equal(true); + expect(processedBid.currency).to.equal(responseCurrency); + }); + + it('returns an valid bid response on sucessful video request', () => { + let incomingRequestId = 'XXtesting-275XX'; + let responsePrice = 6 + + let responseCreative = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<VAST version=\"2.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"vast.xsd\">\n <Ad id=\"01-1101011\">\n <InLine>\n <AdSystem version=\"2.0\">Mediatastic</AdSystem>\n <Impression><![CDATA[https://tas-qa.rockyou.com/servlet/rotator/278/0/impr?ajkey=V1213221C05J-573J8400I2011329C6717J1556I270QI238QQP0G00G0I270127CF0B71G5302EW30061G3302E300001011000G0PH101H36W8a8ae0b482DW4a8db2DW442202DW4ba0c2DX127458f452b1f5H24X24329C6717329C6717448F69C5G0101109G9W9unwrapperG01FFG0G05C]]></Impression><Creatives>\n <Creative>\n <Linear>\n <MediaFiles>\n <MediaFile delivery=\"progressive\" height=\"3\" type=\"video/mp4\" width=\"4\"><![CDATA[https://cdnrockyou-a.akamaihd.net/apps/socialvideoads/ads/mobiletest/kitchenscramble_15s.mp4]]></MediaFile>\n </MediaFiles>\n <Duration><![CDATA[00:00:15]]></Duration>\n <TrackingEvents/>\n <VideoClicks>\n <ClickThrough><![CDATA[https://www.facebook.com/KitchenScramble]]></ClickThrough>\n <ClickTracking><![CDATA[https://tas-qa.rockyou.com/servlet/rotator/278/0/ch?ajkey=V1213221C05J-573J8400I2011329C6717J1556I270QI238QQP0G00G0I270127CF0B71G5302EW30061G3302E300001011000G0PH101H36W8a8ae0b482DW4a8db2DW442202DW4ba0c2DX127458f452b1f5H24X24329C6717329C6717448F69C5G0101109G9W9unwrapperG01FFG0G05C]]></ClickTracking></VideoClicks>\n </Linear>\n </Creative>\n </Creatives>\n </InLine>\n </Ad>\n</VAST>'; + + let responseCreativeId = '1556'; + let responseCurrency = 'USD'; + + let responseWidth = 284; + let responseHeight = 285; + let responseTtl = 286; + + let sampleResponse = { + id: '1234567890', + seatbid: [ + { + bid: [ + { + id: 'a8ae0b48-a8db-4220-ba0c-7458f452b1f5', + impid: '1', + price: responsePrice, + adid: responseCreativeId, + adm: responseCreative, + cid: '270', + attr: [], + w: responseWidth, + h: responseHeight, + ext: { + ttl: responseTtl + } + } + ], + seat: '201', + group: 0 + } + ], + bidid: 'a8ae0b48-a8db-4220-ba0c-7458f452b1f5', + cur: 'USD' + }; + + let sampleRequest = { + bidId: incomingRequestId, + mediaTypes: { + video: { + } + }, + requestId: incomingRequestId + }; + + let result = spec.interpretResponse( + { + body: sampleResponse + }, + sampleRequest + ); + + expect(result.length).to.equal(1); + + let processedBid = result[0]; + + // expect(processedBid.requestId).to.equal(incomingRequestId); + expect(processedBid.cpm).to.equal(responsePrice); + expect(processedBid.width).to.equal(responseWidth); + expect(processedBid.height).to.equal(responseHeight); + expect(processedBid.ad).to.equal(null); + expect(processedBid.ttl).to.equal(responseTtl); + expect(processedBid.creativeId).to.equal(responseCreativeId); + expect(processedBid.netRevenue).to.equal(true); + expect(processedBid.currency).to.equal(responseCurrency); + expect(processedBid.vastXml).to.equal(responseCreative); + }); + + it('generates event callbacks as expected', () => { + let tally = {}; + let renderer = { + handleVideoEvent: (eventObject) => { + let eventName = eventObject.eventName; + if (tally[eventName]) { + tally[eventName] = tally[eventName] + 1; + } else { + tally[eventName] = 1; + } + } + }; + + let callbacks = internals.playerCallbacks(renderer); + + let validCallbacks = ['LOAD', 'IMPRESSION', 'COMPLETE', 'ERROR']; + + validCallbacks.forEach(event => { + callbacks('n/a', event); + }); + + let callbackKeys = Object.keys(tally); + expect(callbackKeys.length).to.equal(3); + expect(tally['loaded']).to.equal(1); + expect(tally['impression']).to.equal(1); + expect(tally['ended']).to.equal(2); + }); + + it('generates a renderer that will hide on complete', () => { + let elementName = 'test_element_id'; + let selector = `#${elementName}`; + + let mockElement = { + style: { + display: 'some' + } + }; + + document.querySelector = (name) => { + if (name === selector) { + return mockElement; + } else { + return null; + } + }; + + let renderer = internals.generateRenderer({}, elementName); + + renderer.handlers['ended'](); + + expect(mockElement.style.display).to.equal('none'); + }) + }); +}); diff --git a/test/spec/modules/roxotAnalyticsAdapter_spec.js b/test/spec/modules/roxotAnalyticsAdapter_spec.js index 81faf164434..f2db77892f6 100644 --- a/test/spec/modules/roxotAnalyticsAdapter_spec.js +++ b/test/spec/modules/roxotAnalyticsAdapter_spec.js @@ -5,13 +5,24 @@ let adaptermanager = require('src/adaptermanager'); let constants = require('src/constants.json'); describe('Roxot Prebid Analytic', function () { + let xhr; + before(() => { + xhr = sinon.useFakeXMLHttpRequest(); + }) + after(() => { + roxotAnalytic.disableAnalytics(); + xhr.restore(); + }); + describe('enableAnalytics', function () { beforeEach(() => { sinon.spy(roxotAnalytic, 'track'); + sinon.stub(events, 'getEvents').returns([]); }); afterEach(() => { roxotAnalytic.track.restore(); + events.getEvents.restore(); }); it('should catch all events', function () { adaptermanager.registerAnalyticsAdapter({ diff --git a/test/spec/modules/roxotBidAdapter_spec.js b/test/spec/modules/roxotBidAdapter_spec.js deleted file mode 100644 index af7bef291e1..00000000000 --- a/test/spec/modules/roxotBidAdapter_spec.js +++ /dev/null @@ -1,123 +0,0 @@ -describe('Roxot adapter tests', function() { - const expect = require('chai').expect; - const adapter = require('modules/roxotBidAdapter'); - const bidmanager = require('src/bidmanager'); - - describe('roxotResponseHandler', function () { - it('should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.roxotResponseHandler).to.exist.and.to.be.a('function'); - }); - - it('should add empty bid responses if no bids returned', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var bidderRequest = { - bidderCode: 'roxot', - bids: [ - { - bidId: 'id1', - bidder: 'roxot', - sizes: [[320, 50]], - placementCode: 'div-gpt-ad-12345-1' - }, - { - bidId: 'id2', - bidder: 'roxot', - sizes: [[320, 50]], - placementCode: 'div-gpt-ad-12345-2' - }, - ] - }; - - // no bids returned in the response. - var response = { - 'id': '123', - 'bids': [] - }; - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - - // adapter needs to be called, in order for the stub to register. - adapter(); - - $$PREBID_GLOBAL$$.roxotResponseHandler(response); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - var bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0]; - var bidObject2 = stubAddBidResponse.getCall(1).args[1]; - - expect(bidPlacementCode1).to.equal('div-gpt-ad-12345-1'); - expect(bidObject1.getStatusCode()).to.equal(2); - expect(bidObject1.bidderCode).to.equal('roxot'); - - expect(bidPlacementCode2).to.equal('div-gpt-ad-12345-2'); - expect(bidObject2.getStatusCode()).to.equal(2); - expect(bidObject2.bidderCode).to.equal('roxot'); - - stubAddBidResponse.restore(); - }); - - it('should add a bid response for bids returned and empty bid responses for the rest', () => { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - var bidderRequest = { - bidderCode: 'roxot', - bids: [ - { - bidId: 'id1', - bidder: 'roxot', - sizes: [[320, 50]], - placementCode: 'div-gpt-ad-12345-1' - }, - { - bidId: 'id2', - bidder: 'roxot', - sizes: [[320, 50]], - placementCode: 'div-gpt-ad-12345-2' - }, - ] - }; - - // Returning a single bid in the response. - var response = { - 'id': '12345', - 'bids': [ - { - 'bidId': 'id1', - 'cpm': 0.09, - 'nurl': 'http://roxot.example.com', - 'adm': '<<creative>>', - 'h': 320, - 'w': 50 - } - ]}; - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - - // adapter needs to be called, in order for the stub to register. - adapter(); - - $$PREBID_GLOBAL$$.roxotResponseHandler(response); - - var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - var bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0]; - var bidObject2 = stubAddBidResponse.getCall(1).args[1]; - - expect(bidPlacementCode1).to.equal('div-gpt-ad-12345-1'); - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.bidderCode).to.equal('roxot'); - expect(bidObject1.cpm).to.equal(0.09); - expect(bidObject1.height).to.equal(320); - expect(bidObject1.width).to.equal(50); - expect(bidObject1.ad).to.equal('<<creative>><img src="http://roxot.example.com">'); - - expect(bidPlacementCode2).to.equal('div-gpt-ad-12345-2'); - expect(bidObject2.getStatusCode()).to.equal(2); - expect(bidObject2.bidderCode).to.equal('roxot'); - - stubAddBidResponse.restore(); - }); - }); -}); diff --git a/test/spec/modules/rtbdemandAdkBidAdapter_spec.js b/test/spec/modules/rtbdemandAdkBidAdapter_spec.js new file mode 100644 index 00000000000..2bd4ea4ce98 --- /dev/null +++ b/test/spec/modules/rtbdemandAdkBidAdapter_spec.js @@ -0,0 +1,268 @@ +import {expect} from 'chai'; +import {spec} from 'modules/rtbdemandAdkBidAdapter'; +import * as utils from 'src/utils'; + +describe('RtbdemandAdk adapter', () => { + const bid1_zone1 = { + bidder: 'rtbdemandadk', + bidId: 'Bid_01', + params: {zoneId: 1, host: 'rtb.rtbdemand.com'}, + placementCode: 'ad-unit-1', + sizes: [[300, 250], [300, 200]] + }, bid2_zone2 = { + bidder: 'rtbdemandadk', + bidId: 'Bid_02', + params: {zoneId: 2, host: 'rtb.rtbdemand.com'}, + placementCode: 'ad-unit-2', + sizes: [[728, 90]] + }, bid3_host2 = { + bidder: 'rtbdemandadk', + bidId: 'Bid_02', + params: {zoneId: 1, host: 'rtb-private.rtbdemand.com'}, + placementCode: 'ad-unit-2', + sizes: [[728, 90]] + }, bid_without_zone = { + bidder: 'rtbdemandadk', + bidId: 'Bid_W', + params: {host: 'rtb-private.rtbdemand.com'}, + placementCode: 'ad-unit-1', + sizes: [[728, 90]] + }, bid_without_host = { + bidder: 'rtbdemandadk', + bidId: 'Bid_W', + params: {zoneId: 1}, + placementCode: 'ad-unit-1', + sizes: [[728, 90]] + }, bid_with_wrong_zoneId = { + bidder: 'rtbdemandadk', + bidId: 'Bid_02', + params: {zoneId: 'wrong id', host: 'rtb.rtbdemand.com'}, + placementCode: 'ad-unit-2', + sizes: [[728, 90]] + }, bid_video = { + bidder: 'rtbdemandadk', + bidId: 'Bid_Video', + sizes: [640, 480], + mediaType: 'video', + params: { + zoneId: 1, + host: 'rtb.rtbdemand.com', + video: { + mimes: ['video/mp4', 'video/webm', 'video/x-flv'] + } + }, + placementCode: 'ad-unit-1' + }; + + const bidResponse1 = { + id: 'bid1', + seatbid: [{ + bid: [{ + id: '1', + impid: 'Bid_01', + crid: '100_001', + price: 3.01, + nurl: 'https://rtb.com/win?i=ZjKoPYSFI3Y_0', + adm: '<!-- admarkup here -->', + w: 300, + h: 250 + }] + }], + cur: 'USD', + ext: { + adk_usersync: ['http://adk.sync.com/sync'] + } + }, bidResponse2 = { + id: 'bid2', + seatbid: [{ + bid: [{ + id: '2', + impid: 'Bid_02', + crid: '100_002', + price: 1.31, + adm: '<!-- admarkup here -->', + w: 300, + h: 250 + }] + }], + cur: 'USD' + }, videoBidResponse = { + id: '47ce4badcf7482', + seatbid: [{ + bid: [{ + id: 'sZSYq5zYMxo_0', + impid: 'Bid_Video', + crid: '100_003', + price: 0.00145, + adid: '158801', + nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl', + cid: '16855' + }] + }], + cur: 'USD' + }, usersyncOnlyResponse = { + id: 'nobid1', + ext: { + adk_usersync: ['http://adk.sync.com/sync'] + } + }; + + describe('input parameters validation', () => { + it('empty request shouldn\'t generate exception', () => { + expect(spec.isBidRequestValid({ + bidderCode: 'rtbdemandadk' + })).to.be.equal(false); + }); + + it('request without zone shouldn\'t issue a request', () => { + expect(spec.isBidRequestValid(bid_without_zone)).to.be.equal(false); + }); + + it('request without host shouldn\'t issue a request', () => { + expect(spec.isBidRequestValid(bid_without_host)).to.be.equal(false); + }); + + it('empty request shouldn\'t generate exception', () => { + expect(spec.isBidRequestValid(bid_with_wrong_zoneId)).to.be.equal(false); + }); + }); + + describe('banner request building', () => { + let bidRequest; + before(() => { + let wmock = sinon.stub(utils, 'getTopWindowLocation').callsFake(() => ({ + protocol: 'https:', + hostname: 'example.com', + host: 'example.com', + pathname: '/index.html', + href: 'https://example.com/index.html' + })); + let dntmock = sinon.stub(utils, 'getDNT').callsFake(() => true); + let request = spec.buildRequests([bid1_zone1])[0]; + bidRequest = JSON.parse(request.data.r); + wmock.restore(); + dntmock.restore(); + }); + + it('should be a first-price auction', () => { + expect(bidRequest).to.have.property('at', 1); + }); + + it('should have banner object', () => { + expect(bidRequest.imp[0]).to.have.property('banner'); + }); + + it('should have w/h', () => { + 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}]); + }); + + it('should respect secure connection', () => { + expect(bidRequest.imp[0]).to.have.property('secure', 1); + }); + + it('should have tagid', () => { + expect(bidRequest.imp[0]).to.have.property('tagid', 'ad-unit-1'); + }); + + it('should create proper site block', () => { + expect(bidRequest.site).to.have.property('domain', 'example.com'); + expect(bidRequest.site).to.have.property('page', 'https://example.com/index.html'); + }); + + it('should fill device with caller macro', () => { + expect(bidRequest).to.have.property('device'); + expect(bidRequest.device).to.have.property('ip', 'caller'); + expect(bidRequest.device).to.have.property('ua', 'caller'); + expect(bidRequest.device).to.have.property('dnt', 1); + }); + }); + + describe('video request building', () => { + let bidRequest; + + before(() => { + let request = spec.buildRequests([bid_video])[0]; + bidRequest = JSON.parse(request.data.r); + }); + + it('should have video object', () => { + expect(bidRequest.imp[0]).to.have.property('video'); + }); + + it('should have h/w', () => { + expect(bidRequest.imp[0].video).to.have.property('w', 640); + expect(bidRequest.imp[0].video).to.have.property('h', 480); + }); + + it('should have tagid', () => { + expect(bidRequest.imp[0]).to.have.property('tagid', 'ad-unit-1'); + }); + }); + + describe('requests routing', () => { + it('should issue a request for each host', () => { + let pbRequests = spec.buildRequests([bid1_zone1, bid3_host2]); + expect(pbRequests).to.have.length(2); + expect(pbRequests[0].url).to.have.string(`//${bid1_zone1.params.host}/`); + expect(pbRequests[1].url).to.have.string(`//${bid3_host2.params.host}/`); + }); + + it('should issue a request for each zone', () => { + let pbRequests = spec.buildRequests([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); + }); + }); + + describe('responses processing', () => { + it('should return fully-initialized banner bid-response', () => { + let request = spec.buildRequests([bid1_zone1])[0]; + let resp = spec.interpretResponse({body: bidResponse1}, request)[0]; + expect(resp).to.have.property('requestId', 'Bid_01'); + expect(resp).to.have.property('cpm', 3.01); + expect(resp).to.have.property('width', 300); + expect(resp).to.have.property('height', 250); + expect(resp).to.have.property('creativeId', '100_001'); + expect(resp).to.have.property('currency'); + expect(resp).to.have.property('ttl'); + expect(resp).to.have.property('mediaType', 'banner'); + expect(resp).to.have.property('ad'); + expect(resp.ad).to.have.string('<!-- admarkup here -->'); + }); + + it('should return fully-initialized video bid-response', () => { + let request = spec.buildRequests([bid_video])[0]; + let resp = spec.interpretResponse({body: videoBidResponse}, request)[0]; + expect(resp).to.have.property('requestId', 'Bid_Video'); + expect(resp.mediaType).to.equal('video'); + expect(resp.cpm).to.equal(0.00145); + expect(resp.vastUrl).to.equal('https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl'); + expect(resp.width).to.equal(640); + expect(resp.height).to.equal(480); + }); + + it('should add nurl as pixel for banner response', () => { + let request = spec.buildRequests([bid1_zone1])[0]; + let resp = spec.interpretResponse({body: bidResponse1}, request)[0]; + let expectedNurl = bidResponse1.seatbid[0].bid[0].nurl + '&px=1'; + expect(resp.ad).to.have.string(expectedNurl); + }); + + it('should handle bidresponse with user-sync only', () => { + let request = spec.buildRequests([bid1_zone1])[0]; + let resp = spec.interpretResponse({body: usersyncOnlyResponse}, request); + expect(resp).to.have.length(0); + }); + + it('should perform usersync', () => { + let syncs = spec.getUserSyncs({iframeEnabled: false}, [{body: bidResponse1}]); + expect(syncs).to.have.length(0); + syncs = spec.getUserSyncs({iframeEnabled: true}, [{body: bidResponse1}]); + expect(syncs).to.have.length(1); + expect(syncs[0]).to.have.property('type', 'iframe'); + expect(syncs[0]).to.have.property('url', 'http://adk.sync.com/sync'); + }); + }); +}); diff --git a/test/spec/modules/rtbdemandBidAdapter_spec.js b/test/spec/modules/rtbdemandBidAdapter_spec.js new file mode 100644 index 00000000000..20d3e410aee --- /dev/null +++ b/test/spec/modules/rtbdemandBidAdapter_spec.js @@ -0,0 +1,174 @@ +import { expect } from 'chai'; +import { spec } from 'modules/rtbdemandBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('rtbdemandAdapter', () => { + 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', () => { + let bid = { + 'bidder': 'rtbdemand', + 'params': { + 'zoneid': '37', + 'floor': '0.05', + 'server': 'bidding.rtbdemand.com', + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'zoneid': '37', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'zoneid': 0, + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidderRequest = { + bidderCode: 'rtbdemand', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + bidderRequestId: '178e34bad3658f', + bids: [ + { + bidder: 'rtbdemand', + params: { + zoneid: '37', + floor: '0.05', + server: 'bidding.rtbdemand.com', + }, + placementCode: '/19968336/header-bid-tag-0', + sizes: [[300, 250], [320, 50]], + bidId: '2ffb201a808da7', + bidderRequestId: '178e34bad3658f', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + }, + { + bidder: 'rtbdemand', + params: { + zoneid: '37', + floor: '0.05', + server: 'bidding.rtbdemand.com', + }, + placementCode: '/19968336/header-bid-tag-0', + sizes: [[728, 90], [320, 50]], + bidId: '2ffb201a808da7', + bidderRequestId: '178e34bad3658f', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + ], + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000 + }; + + it('should add source and verison to the tag', () => { + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const payload = request.data; + expect(payload.from).to.exist; + expect(payload.v).to.exist; + expect(payload.request_id).to.exist; + expect(payload.imp_id).to.exist; + expect(payload.aff).to.exist; + expect(payload.bid_floor).to.exist; + expect(payload.charset).to.exist; + expect(payload.site_domain).to.exist; + expect(payload.site_page).to.exist; + expect(payload.subid).to.exist; + expect(payload.flashver).to.exist; + expect(payload.tmax).to.exist; + }); + + it('sends bid request to ENDPOINT via GET', () => { + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.url).to.equal('//bidding.rtbdemand.com/hb'); + expect(request.method).to.equal('GET'); + }); + }) + + describe('interpretResponse', () => { + let response = { + 'id': '543210', + 'seatbid': [ { + 'bid': [ { + 'id': '1111111', + 'impid': 'bidId-123456-1', + 'w': 728, + 'h': 90, + 'price': 0.09, + 'adm': '<!-- Creative -->', + } ], + } ] + }; + + it('should get correct bid response', () => { + let expectedResponse = [ + { + requestId: 'bidId-123456-1', + creativeId: 'bidId-123456-1', + cpm: 0.09, + width: 728, + height: 90, + ad: '<!-- Creative -->', + netRevenue: true, + currency: 'USD', + ttl: 360, + } + ]; + + let result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', () => { + let response = { + 'id': '543210', + 'seatbid': [ ] + }; + + let result = spec.interpretResponse({ body: response }); + expect(result.length).to.equal(0); + }); + }); + + describe('user sync', () => { + const syncUrl = '//bidding.rtbdemand.com/delivery/matches.php?type=iframe'; + + it('should register the sync iframe', () => { + expect(spec.getUserSyncs({})).to.be.undefined; + expect(spec.getUserSyncs({iframeEnabled: false})).to.be.undefined; + 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(syncUrl); + }); + }); +}); diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js new file mode 100644 index 00000000000..69bd3f40f72 --- /dev/null +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -0,0 +1,129 @@ +import { expect } from 'chai'; +import { 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'; + +/** + * Helpers + */ + +function buildEndpointUrl(region) { + return 'https://' + region + '.' + ENDPOINT_URL; +} + +/** + * endof Helpers + */ + +describe('RTBHouseAdapter', () => { + 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', () => { + let bid = { + 'bidder': 'rtbhouse', + 'params': { + 'publisherId': 'PREBID_TEST', + 'region': 'prebid-eu' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }; + + 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', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'someIncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + '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' + } + ]; + + it('should build test param into the request', () => { + let builtTestRequest = spec.buildRequests(bidRequests).data; + expect(JSON.parse(builtTestRequest).test).to.equal(1); + }); + + it('sends bid request to ENDPOINT via POST', () => { + 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)); + expect(request.method).to.equal('POST'); + }); + }) + + describe('interpretResponse', () => { + let response = [{ + 'id': 'bidder_imp_identifier', + 'impid': '552b8922e28f27', + 'price': 0.5, + 'adid': 'Ad_Identifier', + 'adm': '<!-- test creative -->', + 'adomain': ['rtbhouse.com'], + 'cid': 'Ad_Identifier', + 'w': 300, + 'h': 250 + }]; + + it('should get correct bid response', () => { + let expectedResponse = [ + { + 'requestId': '552b8922e28f27', + 'cpm': 0.5, + 'creativeId': 29681110, + 'width': 300, + 'height': 250, + 'ad': '<!-- test creative -->', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'netRevenue': true + } + ]; + let bidderRequest; + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', () => { + let response = ''; + let bidderRequest; + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..38f8b726cd1 --- /dev/null +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -0,0 +1,632 @@ +import adaptermanager from 'src/adaptermanager'; +import rubiconAnalyticsAdapter, { SEND_TIMEOUT } from 'modules/rubiconAnalyticsAdapter'; +import CONSTANTS from 'src/constants.json'; +import { config } from 'src/config'; + +let Ajv = require('ajv'); +let schema = require('./rubiconAnalyticsSchema.json'); +let ajv = new Ajv({ + allErrors: true +}); + +let validator = ajv.compile(schema); + +function validate(message) { + validator(message); + expect(validator.errors).to.deep.equal(null); +} + +// using es6 "import * as events from 'src/events'" causes the events.getEvents stub not to work... +let events = require('src/events'); +let ajax = require('src/ajax'); +let utils = require('src/utils'); + +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BID_WON, + BID_TIMEOUT, + SET_TARGETING + } +} = CONSTANTS; + +const BID = { + 'bidder': 'rubicon', + 'width': 640, + 'height': 480, + 'mediaType': 'video', + 'statusMessage': 'Bid available', + 'adId': '2ecff0db240757', + 'source': 'client', + 'requestId': '2ecff0db240757', + 'currency': 'USD', + 'creativeId': '3571560', + 'cpm': 1.22752, + 'ttl': 300, + 'netRevenue': false, + 'ad': '<html></html>', + 'rubiconTargeting': { + 'rpfl_elemid': '/19968336/header-bid-tag-0', + 'rpfl_14062': '2_tier0100' + }, + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'responseTimestamp': 1519149629415, + 'requestTimestamp': 1519149628471, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'timeToRespond': 944, + 'pbLg': '1.00', + 'pbMg': '1.20', + 'pbHg': '1.22', + 'pbAg': '1.20', + 'pbDg': '1.22', + 'pbCg': '', + 'size': '640x480', + 'adserverTargeting': { + 'hb_bidder': 'rubicon', + 'hb_adid': '2ecff0db240757', + 'hb_pb': 1.20, + 'hb_size': '640x480', + 'hb_source': 'client' + }, + getStatusCode() { + return 1; + } +}; + +const BID2 = Object.assign({}, BID, { + adUnitCode: '/19968336/header-bid-tag1', + adId: '3bd4ebb1c900e2', + requestId: '3bd4ebb1c900e2', + width: 728, + height: 90, + mediaType: 'banner', + cpm: 1.52, + source: 'server', + serverResponseTimeMs: 42, + rubiconTargeting: { + 'rpfl_elemid': '/19968336/header-bid-tag1', + 'rpfl_14062': '2_tier0100' + }, + adserverTargeting: { + 'hb_bidder': 'rubicon', + 'hb_adid': '3bd4ebb1c900e2', + 'hb_pb': '1.500', + 'hb_size': '728x90', + 'hb_source': 'server' + } +}); + +const MOCK = { + SET_TARGETING: { + [BID.adUnitCode]: BID.adserverTargeting, + [BID2.adUnitCode]: BID2.adserverTargeting + }, + AUCTION_INIT: { + 'timestamp': 1519767010567, + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'timeout': 3000 + }, + BID_REQUESTED: { + 'bidder': 'rubicon', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'rubicon', + 'params': { + 'accountId': '14062', + 'siteId': '70608', + 'zoneId': '335918', + 'userId': '12346', + 'keywords': ['a', 'b', 'c'], + 'inventory': 'test', + 'visitor': {'ucat': 'new', 'lastsearch': 'iphone'}, + 'position': 'btf', + 'video': { + 'language': 'en', + 'playerHeight': 480, + 'playerWidth': 640, + 'size_id': 203, + 'skip': 1, + 'skipdelay': 15, + 'aeParams': { + 'p_aso.video.ext.skip': '1', + 'p_aso.video.ext.skipdelay': '15' + } + } + }, + 'mediaType': 'video', + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + }, + { + 'bidder': 'rubicon', + 'params': { + 'accountId': '14062', + 'siteId': '70608', + 'zoneId': '335918', + 'userId': '12346', + 'keywords': ['a', 'b', 'c'], + 'inventory': {'rating': '4-star', 'prodtype': 'tech'}, + 'visitor': {'ucat': 'new', 'lastsearch': 'iphone'}, + 'position': 'atf' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[1000, 300], [970, 250], [728, 90]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', + 'sizes': [[1000, 300], [970, 250], [728, 90]], + 'bidId': '3bd4ebb1c900e2', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + } + ], + 'auctionStart': 1519149536560, + 'timeout': 5000, + 'start': 1519149562216 + }, + BID_RESPONSE: [ + BID, + BID2 + ], + AUCTION_END: { + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + }, + BID_WON: [ + Object.assign({}, BID, { + 'status': 'rendered' + }), + Object.assign({}, BID2, { + 'status': 'rendered' + }) + ], + BID_TIMEOUT: [ + { + 'bidId': '2ecff0db240757', + 'bidder': 'rubicon', + 'adUnitCode': '/19968336/header-bid-tag-0', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + } + ] +}; + +const ANALYTICS_MESSAGE = { + 'eventTimeMillis': 1519767013781, + 'integration': 'pbjs', + 'version': '$prebid.version$', + 'referrerUri': 'http://www.test.com/page.html', + 'auctions': [ + { + 'clientTimeoutMillis': 3000, + 'serverTimeoutMillis': 1000, + 'accountId': 1001, + 'samplingFactor': 1, + 'adUnits': [ + { + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'videoAdFormat': 'outstream', + 'mediaTypes': [ + 'video' + ], + 'dimensions': [ + { + 'width': 640, + 'height': 480 + } + ], + 'status': 'success', + 'adserverTargeting': { + 'hb_bidder': 'rubicon', + 'hb_adid': '2ecff0db240757', + 'hb_pb': '1.200', + 'hb_size': '640x480', + 'hb_source': 'client' + }, + 'bids': [ + { + 'bidder': 'rubicon', + 'bidId': '2ecff0db240757', + 'status': 'success', + 'source': 'client', + 'clientLatencyMillis': 3214, + 'params': { + 'accountId': '14062', + 'siteId': '70608', + 'zoneId': '335918' + }, + 'bidResponse': { + 'bidPriceUSD': 1.22752, + 'dimensions': { + 'width': 640, + 'height': 480 + }, + 'mediaType': 'video' + } + } + ] + }, + { + 'adUnitCode': '/19968336/header-bid-tag1', + 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', + 'mediaTypes': [ + 'banner' + ], + 'dimensions': [ + { + 'width': 1000, + 'height': 300 + }, + { + 'width': 970, + 'height': 250 + }, + { + 'width': 728, + 'height': 90 + } + ], + 'status': 'success', + 'adserverTargeting': { + 'hb_bidder': 'rubicon', + 'hb_adid': '3bd4ebb1c900e2', + 'hb_pb': '1.500', + 'hb_size': '728x90', + 'hb_source': 'server' + }, + 'bids': [ + { + 'bidder': 'rubicon', + 'bidId': '3bd4ebb1c900e2', + 'status': 'success', + 'source': 'server', + 'clientLatencyMillis': 3214, + 'serverLatencyMillis': 42, + 'params': { + 'accountId': '14062', + 'siteId': '70608', + 'zoneId': '335918' + }, + 'bidResponse': { + 'bidPriceUSD': 1.52, + 'dimensions': { + 'width': 728, + 'height': 90 + }, + 'mediaType': 'banner' + } + } + ] + } + ] + } + ], + 'bidsWon': [ + { + 'bidder': 'rubicon', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'adUnitCode': '/19968336/header-bid-tag-0', + 'bidId': '2ecff0db240757', + 'status': 'success', + 'source': 'client', + 'clientLatencyMillis': 3214, + 'samplingFactor': 1, + 'accountId': 1001, + 'params': { + 'accountId': '14062', + 'siteId': '70608', + 'zoneId': '335918' + }, + 'videoAdFormat': 'outstream', + 'mediaTypes': [ + 'video' + ], + 'adserverTargeting': { + 'hb_bidder': 'rubicon', + 'hb_adid': '2ecff0db240757', + 'hb_pb': '1.200', + 'hb_size': '640x480', + 'hb_source': 'client' + }, + 'bidResponse': { + 'bidPriceUSD': 1.22752, + 'dimensions': { + 'width': 640, + 'height': 480 + }, + 'mediaType': 'video' + }, + 'bidwonStatus': 'success' + }, + { + 'bidder': 'rubicon', + 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', + 'adUnitCode': '/19968336/header-bid-tag1', + 'bidId': '3bd4ebb1c900e2', + 'status': 'success', + 'source': 'server', + 'clientLatencyMillis': 3214, + 'serverLatencyMillis': 42, + 'samplingFactor': 1, + 'accountId': 1001, + 'params': { + 'accountId': '14062', + 'siteId': '70608', + 'zoneId': '335918' + }, + 'mediaTypes': [ + 'banner' + ], + 'adserverTargeting': { + 'hb_bidder': 'rubicon', + 'hb_adid': '3bd4ebb1c900e2', + 'hb_pb': '1.500', + 'hb_size': '728x90', + 'hb_source': 'server' + }, + 'bidResponse': { + 'bidPriceUSD': 1.52, + 'dimensions': { + 'width': 728, + 'height': 90 + }, + 'mediaType': 'banner' + }, + 'bidwonStatus': 'success' + } + ] +}; + +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(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('rubicon analytics adapter', () => { + let sandbox; + let xhr; + let requests; + let oldScreen; + let clock; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + + xhr = sandbox.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + + sandbox.stub(events, 'getEvents').returns([]); + + sandbox.stub(utils, 'getTopWindowUrl').returns('http://www.test.com/page.html'); + + clock = sandbox.useFakeTimers(1519767013781); + + config.setConfig({ + s2sConfig: { + timeout: 1000, + accountId: 10000, + } + }) + }); + + afterEach(() => { + sandbox.restore(); + config.resetConfig(); + }); + + it('should require accountId', () => { + sandbox.stub(utils, 'logError'); + + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event' + } + }); + + expect(utils.logError.called).to.equal(true); + }); + + it('should require endpoint', () => { + sandbox.stub(utils, 'logError'); + + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + accountId: 1001 + } + }); + + expect(utils.logError.called).to.equal(true); + }); + + describe('sampling', () => { + beforeEach(() => { + sandbox.stub(Math, 'random').returns(0.08); + sandbox.stub(utils, 'logError'); + }); + + afterEach(() => { + rubiconAnalyticsAdapter.disableAnalytics(); + }); + + describe('with options.samplingFactor', () => { + it('should sample', () => { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + samplingFactor: 10 + } + }); + + performStandardAuction(); + + expect(requests.length).to.equal(1); + }); + + it('should unsample', () => { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + samplingFactor: 20 + } + }); + + performStandardAuction(); + + expect(requests.length).to.equal(0); + }); + + it('should throw errors for invalid samplingFactor', () => { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + samplingFactor: 30 + } + }); + + performStandardAuction(); + + expect(requests.length).to.equal(0); + expect(utils.logError.called).to.equal(true); + }); + }); + describe('with options.sampling', () => { + it('should sample', () => { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + sampling: 0.1 + } + }); + + performStandardAuction(); + + expect(requests.length).to.equal(1); + }); + + it('should unsample', () => { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + sampling: 0.05 + } + }); + + performStandardAuction(); + + expect(requests.length).to.equal(0); + }); + + it('should throw errors for invalid samplingFactor', () => { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001, + sampling: 1 / 30 + } + }); + + performStandardAuction(); + + expect(requests.length).to.equal(0); + expect(utils.logError.called).to.equal(true); + }); + }); + }); + + describe('when handling events', () => { + beforeEach(() => { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: '1001' + } + }); + }); + + afterEach(() => { + rubiconAnalyticsAdapter.disableAnalytics(); + }); + + it('should build a batched message from prebid events', () => { + performStandardAuction(); + + expect(requests.length).to.equal(1); + let request = requests[0]; + + expect(request.url).to.equal('//localhost:9999/event'); + + let message = JSON.parse(request.requestBody); + validate(message); + + expect(message).to.deep.equal(ANALYTICS_MESSAGE); + }); + + it('should send batched message without BID_WON if necessary and further BID_WON events individually', () => { + 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(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + + clock.tick(SEND_TIMEOUT + 1000); + + events.emit(BID_WON, MOCK.BID_WON[1]); + + expect(requests.length).to.equal(2); + + let message = JSON.parse(requests[0].requestBody); + validate(message); + expect(message.bidsWon.length).to.equal(1); + expect(message.auctions).to.deep.equal(ANALYTICS_MESSAGE.auctions); + expect(message.bidsWon[0]).to.deep.equal(ANALYTICS_MESSAGE.bidsWon[0]); + + message = JSON.parse(requests[1].requestBody); + validate(message); + expect(message.bidsWon.length).to.equal(1); + expect(message).to.not.have.property('auctions'); + expect(message.bidsWon[0]).to.deep.equal(ANALYTICS_MESSAGE.bidsWon[1]); + }); + + it('should properly mark bids as timed out', () => { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + clock.tick(SEND_TIMEOUT + 1000); + + expect(requests.length).to.equal(1); + + let message = JSON.parse(requests[0].requestBody); + validate(message); + let timedOutBid = message.auctions[0].adUnits[0].bids[0]; + expect(timedOutBid.status).to.equal('error'); + expect(timedOutBid.error.code).to.equal('timeout-error'); + expect(timedOutBid).to.not.have.property('bidResponse'); + }); + }); +}); diff --git a/test/spec/modules/rubiconAnalyticsSchema.json b/test/spec/modules/rubiconAnalyticsSchema.json new file mode 100644 index 00000000000..cc4ad20db19 --- /dev/null +++ b/test/spec/modules/rubiconAnalyticsSchema.json @@ -0,0 +1,357 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Prebid Auctions", + "description": "A batched data object describing the lifecycle of an auction or multiple auction across a single page view.", + "type": "object", + "required": [ + "eventTimeMillis", + "integration", + "version" + ], + "anyOf": [ + { + "required": [ + "auctions" + ] + }, + { + "required": [ + "bidsWon" + ] + } + ], + "properties": { + "eventTimeMillis": { + "type": "integer", + "description": "Unix timestamp of time of creation for this batched event in milliseconds." + }, + "integration": { + "type": "string", + "description": "Integration type that generated this event.", + "default": "pbjs" + }, + "version": { + "type": "string", + "description": "Version of Prebid.js responsible for the auctions contained within." + }, + "auctions": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "adUnits", + "samplingFactor" + ], + "properties": { + "clientTimeoutMillis": { + "type": "integer", + "description": "Timeout given in client for given auction in milliseconds (if applicable)." + }, + "serverTimeoutMillis": { + "type": "integer", + "description": "Timeout configured for server adapter request in milliseconds (if applicable)." + }, + "accountId": { + "type": "number", + "description": "The account id for prebid server (if applicable)." + }, + "samplingFactor": { + "$ref": "#/definitions/samplingFactor" + }, + "adUnits": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "description": "An array of adUnits involved in this auction.", + "required": [ + "status", + "adUnitCode", + "transactionId", + "mediaTypes", + "dimensions", + "bids" + ], + "properties": { + "status": { + "type": "string", + "description": "The status of the adUnit" + }, + "adUnitCode": { + "type": "string", + "description": "The adUnit.code identifier" + }, + "transactionId": { + "type": "string", + "description": "The UUID generated id to represent this adunit in this auction." + }, + "adSlot": { + "type": "string" + }, + "mediaTypes": { + "$ref": "#/definitions/mediaTypes" + }, + "videoAdFormat": { + "$ref": "#/definitions/videoAdFormat" + }, + "dimensions": { + "type": "array", + "description": "All valid sizes included in this auction (note: may be sizeConfig filtered).", + "minItems": 1, + "items": { + "$ref": "#/definitions/dimensions" + } + }, + "adserverTargeting": { + "$ref": "#/definitions/adserverTargeting" + }, + "bids": { + "type": "array", + "description": "An array that contains a combination of the bids from the adUnit combined with their responses.", + "minItems": 1, + "items": { + "$ref": "#/definitions/bid" + } + } + } + } + } + } + } + }, + "bidsWon": { + "type": "array", + "minItems": 1, + "items": { + "allOf": [ + { + "$ref": "#/definitions/bid" + }, + { + "required": [ + "transactionId", + "accountId", + "samplingFactor", + "mediaTypes", + "adUnitCode", + "bidwonStatus" + ], + "properties": { + "transactionId": { + "type": "string" + }, + "accountId": { + "type": "number" + }, + "samplingFactor": { + "$ref": "#/definitions/samplingFactor" + }, + "adUnitCode": { + "type": "string" + }, + "videoAdFormat": { + "$ref": "#/definitions/videoAdFormat" + }, + "mediaTypes": { + "$ref": "#/definitions/mediaTypes" + }, + "adserverTargeting": { + "$ref": "#/definitions/adserverTargeting" + }, + "bidwonStatus": { + "description": "Whether the bid was successfully rendered or not", + "type": "string", + "enum": [ + "success", + "error" + ] + } + } + } + ] + } + } + }, + "definitions": { + "adserverTargeting": { + "type": "object", + "description": "The adserverTargeting key/value pairs", + "patternProperties": { + ".+": { + "type": "string" + } + } + }, + "samplingFactor": { + "type": "integer", + "description": "An integer value representing the factor to multiply event count by to receive unsampled count.", + "enum": [ + 1, + 10, + 20, + 40, + 100 + ] + }, + "videoAdFormat": { + "type": "string", + "description": "This value only provided for video specifies the ad format", + "enum": [ + "pre-roll", + "interstitial", + "outstream", + "mid-roll", + "post-roll", + "vertical" + ] + }, + "mediaTypes": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "native", + "video", + "banner" + ] + } + }, + "dimensions": { + "type": "object", + "description": "Size object representing the dimensions of creative in pixels.", + "required": [ + "width", + "height" + ], + "properties": { + "width": { + "type": "integer", + "minimum": 1 + }, + "height": { + "type": "integer", + "minimum": 1 + } + } + }, + "bid": { + "type": "object", + "required": [ + "bidder", + "bidId", + "status", + "source" + ], + "properties": { + "bidder": { + "type": "string" + }, + "bidId": { + "type": "string", + "description": "UUID representing this individual bid request in this auction." + }, + "params": { + "description": "A copy of the bid.params from the adUnit.bids", + "anyOf": [ + { + "type": "object" + }, + { + "$ref": "#/definitions/params/rubicon" + } + ] + }, + "status": { + "type": "string", + "enum": [ + "success", + "no-bid", + "error" + ] + }, + "error": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string", + "enum": [ + "request-error", + "connect-error", + "timeout-error" + ] + }, + "description": { + "type": "string" + } + } + }, + "source": { + "type": "string", + "enum": [ + "client", + "server" + ] + }, + "clientLatencyMillis": { + "type": "integer", + "description": "Latency from auction start to bid response recieved in milliseconds." + }, + "serverLatencyMillis": { + "type": "integer", + "description": "Latency returned by prebid server (response_time_ms)." + }, + "bidResponse": { + "type": "object", + "required": [ + "dimensions", + "mediaType", + "bidPriceUSD" + ], + "properties": { + "dimensions": { + "$ref": "#/definitions/dimensions" + }, + "mediaType": { + "type": "string", + "enum": [ + "native", + "video", + "banner" + ] + }, + "bidPriceUSD": { + "type": "number", + "description": "The bid value denoted in USD" + }, + "dealId": { + "type": "integer", + "description": "The id associated with any potential deals" + } + } + } + } + }, + "params": { + "rubicon": { + "type": "object", + "properties": { + "accountId": { + "type": "number" + }, + "siteId": { + "type": "number" + }, + "zoneId": { + "type": "number" + } + } + } + } + } +} diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 620fc56e516..7dcb5e3bbe6 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1,9 +1,11 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import adapterManager from 'src/adaptermanager'; -import { spec, masSizeOrdering, resetUserSync } from 'modules/rubiconBidAdapter'; -import { parse as parseQuery } from 'querystring'; -import { newBidder } from 'src/adapters/bidderFactory'; -import { userSync } from 'src/userSync'; +import {spec, masSizeOrdering, resetUserSync} from 'modules/rubiconBidAdapter'; +import {parse as parseQuery} from 'querystring'; +import {newBidder} from 'src/adapters/bidderFactory'; +import {userSync} from 'src/userSync'; +import {config} from 'src/config'; +import * as utils from 'src/utils'; var CONSTANTS = require('src/constants.json'); @@ -13,8 +15,50 @@ describe('the rubicon adapter', () => { let sandbox, bidderRequest; + /** + * @param {boolean} [gdprApplies] + */ + function createGdprBidderRequest(gdprApplies) { + if (typeof gdprApplies === 'boolean') { + bidderRequest.gdprConsent = { + 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'gdprApplies': gdprApplies + }; + } else { + bidderRequest.gdprConsent = { + 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' + }; + } + } + function createVideoBidderRequest() { + createGdprBidderRequest(true); + 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, + 'playerHeight': 320, + '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', @@ -28,15 +72,66 @@ describe('the rubicon adapter', () => { 'p_aso.video.ext.skipdelay': '15' } }; + bid.params.secure = false; } function createVideoBidderRequestNoVideo() { + let bid = bidderRequest.bids[0]; + bid.mediaTypes = { + video: { + context: 'instream' + }, + }; + 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' + }, + }; + 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' + } + }; + } + 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 = { @@ -56,7 +151,7 @@ describe('the rubicon adapter', () => { bidderRequest = { bidderCode: 'rubicon', - requestId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', bidderRequestId: '178e34bad3658f', bids: [ { @@ -76,13 +171,15 @@ describe('the rubicon adapter', () => { lastsearch: 'iphone' }, position: 'atf', - referrer: 'localhost' + referrer: 'localhost', + latLong: [40.7608, '111.8910'] }, - placementCode: '/19968336/header-bid-tag-0', + adUnitCode: '/19968336/header-bid-tag-0', + code: 'div-1', sizes: [[300, 250], [320, 50]], bidId: '2ffb201a808da7', bidderRequestId: '178e34bad3658f', - requestId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' } ], @@ -97,27 +194,19 @@ describe('the rubicon adapter', () => { }); describe('MAS mapping / ordering', () => { - it('should not include values without a proper mapping', () => { - // two invalid sizes included: [42, 42], [1, 1] - let ordering = masSizeOrdering([[320, 50], [42, 42], [300, 250], [640, 480], [1, 1], [336, 280]]); - - expect(ordering).to.deep.equal([15, 16, 43, 65]); - }); - it('should sort values without any MAS priority sizes in regular ascending order', () => { - let ordering = masSizeOrdering([[320, 50], [640, 480], [336, 280], [200, 600]]); - + let ordering = masSizeOrdering([126, 43, 65, 16]); expect(ordering).to.deep.equal([16, 43, 65, 126]); }); it('should sort MAS priority sizes in the proper order w/ rest ascending', () => { - let ordering = masSizeOrdering([[320, 50], [160, 600], [640, 480], [300, 250], [336, 280], [200, 600]]); + let ordering = masSizeOrdering([43, 9, 65, 15, 16, 126]); expect(ordering).to.deep.equal([15, 9, 16, 43, 65, 126]); - ordering = masSizeOrdering([[320, 50], [300, 250], [160, 600], [640, 480], [336, 280], [200, 600], [728, 90]]); + ordering = masSizeOrdering([43, 15, 9, 65, 16, 126, 2]); expect(ordering).to.deep.equal([15, 2, 9, 16, 43, 65, 126]); - ordering = masSizeOrdering([[120, 600], [320, 50], [160, 600], [640, 480], [336, 280], [200, 600], [728, 90]]); + ordering = masSizeOrdering([8, 43, 9, 65, 16, 126, 2]); expect(ordering).to.deep.equal([2, 9, 8, 16, 43, 65, 126]); }); }); @@ -126,8 +215,7 @@ describe('the rubicon adapter', () => { describe('for requests', () => { describe('to fastlane', () => { it('should make a well-formed request objects', () => { - sandbox.stub(Math, 'random', () => 0.1); - + sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let data = parseQuery(request.data); @@ -144,7 +232,7 @@ describe('the rubicon adapter', () => { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -152,7 +240,9 @@ describe('the rubicon adapter', () => { 'tg_v.lastsearch': 'iphone', 'tg_i.rating': '5-star', 'tg_i.prodtype': 'tech', - 'rf': 'localhost' + 'rf': 'localhost', + 'p_geo.latitude': '40.7608', + 'p_geo.longitude': '111.8910' }; // test that all values above are both present and correct @@ -166,20 +256,45 @@ describe('the rubicon adapter', () => { }); }); - it('should use rubicon sizes if present', () => { + it('page_url should use params.referrer, config.getConfig("pageUrl"), utils.getTopWindowUrl() in that order', () => { + sandbox.stub(utils, 'getTopWindowUrl').callsFake(() => 'http://www.prebid.org'); + + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(parseQuery(request.data).rf).to.equal('localhost'); + + delete bidderRequest.bids[0].params.referrer; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(parseQuery(request.data).rf).to.equal('http://www.prebid.org'); + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'pageUrl') { + return 'http://www.rubiconproject.com'; + } + return origGetConfig.apply(config, arguments); + }); + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(parseQuery(request.data).rf).to.equal('http://www.rubiconproject.com'); + + bidderRequest.bids[0].params.secure = true; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(parseQuery(request.data).rf).to.equal('http://www.rubiconproject.com'); + }); + + it('should use rubicon sizes if present (including non-mappable sizes)', () => { var sizesBidderRequest = clone(bidderRequest); - sizesBidderRequest.bids[0].params.sizes = [55, 57, 59]; + sizesBidderRequest.bids[0].params.sizes = [55, 57, 59, 801]; let [request] = spec.buildRequests(sizesBidderRequest.bids, sizesBidderRequest); let data = parseQuery(request.data); expect(data['size_id']).to.equal('55'); - expect(data['alt_size_ids']).to.equal('57,59'); + expect(data['alt_size_ids']).to.equal('57,59,801'); }); it('should not validate bid request if no valid sizes', () => { var sizesBidderRequest = clone(bidderRequest); - sizesBidderRequest.bids[0].sizes = [[620, 250], [300, 251]]; + sizesBidderRequest.bids[0].sizes = [[621, 250], [300, 251]]; let result = spec.isBidRequestValid(sizesBidderRequest.bids[0]); @@ -207,9 +322,10 @@ describe('the rubicon adapter', () => { it('should send digitrust params', () => { window.DigiTrust = { - getUser: function() {} + getUser: function () { + } }; - sandbox.stub(window.DigiTrust, 'getUser', () => + sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => ({ success: true, identity: { @@ -252,9 +368,10 @@ describe('the rubicon adapter', () => { it('should not send digitrust params due to optout', () => { window.DigiTrust = { - getUser: function() {} + getUser: function () { + } }; - sandbox.stub(window.DigiTrust, 'getUser', () => + sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => ({ success: true, identity: { @@ -280,9 +397,10 @@ describe('the rubicon adapter', () => { it('should not send digitrust params due to failure', () => { window.DigiTrust = { - getUser: function() {} + getUser: function () { + } }; - sandbox.stub(window.DigiTrust, 'getUser', () => + sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => ({ success: false, identity: { @@ -312,16 +430,14 @@ describe('the rubicon adapter', () => { window.DigiTrust = { getUser: sandbox.spy() }; - origGetConfig = window.$$PREBID_GLOBAL$$.getConfig; }); afterEach(() => { delete window.DigiTrust; - window.$$PREBID_GLOBAL$$.getConfig = origGetConfig; }); it('should send digiTrustId config params', () => { - sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(config, 'getConfig').callsFake((key) => { var config = { digiTrustId: { success: true, @@ -354,7 +470,7 @@ describe('the rubicon adapter', () => { }); it('should not send digiTrustId config params due to optout', () => { - sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(config, 'getConfig').callsFake((key) => { var config = { digiTrustId: { success: true, @@ -383,7 +499,7 @@ describe('the rubicon adapter', () => { }); it('should not send digiTrustId config params due to failure', () => { - sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(config, 'getConfig').callsFake((key) => { var config = { digiTrustId: { success: false, @@ -412,7 +528,7 @@ describe('the rubicon adapter', () => { }); it('should not send digiTrustId config params if they do not exist', () => { - sandbox.stub(window.$$PREBID_GLOBAL$$, 'getConfig', (key) => { + sandbox.stub(config, 'getConfig').callsFake((key) => { var config = {}; return config[key]; }); @@ -431,13 +547,117 @@ describe('the rubicon adapter', () => { expect(window.DigiTrust.getUser.calledOnce).to.equal(true); }); }); + + describe('GDPR consent config', () => { + it('should send "gdpr" and "gdpr_consent", when gdprConsent defines consentString and gdprApplies', () => { + createGdprBidderRequest(true); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + + expect(data['gdpr']).to.equal('1'); + expect(data['gdpr_consent']).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + }); + + it('should send only "gdpr_consent", when gdprConsent defines only consentString', () => { + createGdprBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + + expect(data['gdpr_consent']).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(data['gdpr']).to.equal(undefined); + }); + + it('should not send GDPR params if gdprConsent is not defined', () => { + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + + expect(data['gdpr']).to.equal(undefined); + expect(data['gdpr_consent']).to.equal(undefined); + }); + + it('should set "gdpr" value as 1 or 0, using "gdprApplies" value of either true/false', () => { + createGdprBidderRequest(true); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + expect(data['gdpr']).to.equal('1'); + + createGdprBidderRequest(false); + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + data = parseQuery(request.data); + expect(data['gdpr']).to.equal('0'); + }); + }); }); describe('for video requests', () => { + it('should make a well-formed video request with legacy mediaType config', () => { + createLegacyVideoBidderRequest(); + + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + 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.rp_secure).to.equal(false); + 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.equals('tech'); + + 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'); + }); + it('should make a well-formed video request', () => { createVideoBidderRequest(); - sandbox.stub(Date, 'now', () => + sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); @@ -452,9 +672,13 @@ describe('the rubicon adapter', () => { 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.rp_secure).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) @@ -473,8 +697,8 @@ describe('the rubicon adapter', () => { 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].placementCode); - expect(slot.name).to.equal(bidderRequest.bids[0].placementCode); + 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); @@ -494,10 +718,54 @@ describe('the rubicon adapter', () => { expect(slot.visitor).to.have.property('lastsearch').that.equals('iphone'); }); + it('should send request with proper ad position', () => { + createVideoBidderRequest(); + var positionBidderRequest = clone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'atf'; + let [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + let post = request.data; + let slot = post.slots[0]; + + expect(slot.position).to.equal('atf'); + + positionBidderRequest = clone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'btf'; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + post = request.data; + slot = post.slots[0]; + + expect(slot.position).to.equal('btf'); + + positionBidderRequest = clone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'unknown'; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + post = request.data; + slot = post.slots[0]; + + expect(slot.position).to.equal('unknown'); + + positionBidderRequest = clone(bidderRequest); + positionBidderRequest.bids[0].params.position = '123'; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + post = request.data; + slot = post.slots[0]; + + expect(slot.position).to.equal('unknown'); + + positionBidderRequest = clone(bidderRequest); + delete positionBidderRequest.bids[0].params.position; + expect(positionBidderRequest.bids[0].params.position).to.equal(undefined); + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + post = request.data; + slot = post.slots[0]; + + expect(slot.position).to.equal('unknown'); + }); + it('should allow a floor price override', () => { createVideoBidderRequest(); - sandbox.stub(Date, 'now', () => + sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); @@ -514,32 +782,166 @@ describe('the rubicon adapter', () => { expect(floor).to.equal(3.25); }); - it('should not validate bid request when no video object is passed in', () => { + it('should validate bid request with invalid video if a mediaTypes banner property is defined', () => { + const bidRequest = { + mediaTypes: { + video: { + context: 'instream' + }, + banner: { + sizes: [[300, 250]] + } + }, + params: { + accountId: 1001, + video: {} + }, + sizes: [[300, 250]] + } + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should not validate bid request when a invalid video object and no banner object is passed in', () => { createVideoBidderRequestNoVideo(); - sandbox.stub(Date, 'now', () => + sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); - var floorBidderRequest = clone(bidderRequest); + const bidRequestCopy = clone(bidderRequest.bids[0]); + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - let result = spec.isBidRequestValid(floorBidderRequest.bids[0]); + bidRequestCopy.params.video = {}; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - expect(result).to.equal(false); + bidRequestCopy.params.video = undefined; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + + bidRequestCopy.params.video = 123; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + + bidRequestCopy.params.video = {size_id: undefined}; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + + delete bidRequestCopy.params.video; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + }); + + it('should not validate bid request when an invalid video object is passed in with legacy config mediaType', () => { + createLegacyVideoBidderRequestNoVideo(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + const bidderRequestCopy = clone(bidderRequest); + expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + + bidderRequestCopy.bids[0].params.video = {}; + expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + + bidderRequestCopy.bids[0].params.video = undefined; + expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + + bidderRequestCopy.bids[0].params.video = NaN; + expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + + delete bidderRequestCopy.bids[0].params.video; + expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + }); + + it('should not validate bid request when video is outstream', () => { + createVideoBidderRequestOutstream(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); }); it('should get size from bid.sizes too', () => { createVideoBidderRequestNoPlayer(); - sandbox.stub(Date, 'now', () => + sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); - var floorBidderRequest = clone(bidderRequest); + const bidRequestCopy = clone(bidderRequest); - let [request] = spec.buildRequests(floorBidderRequest.bids, floorBidderRequest); - let post = request.data; + let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + + expect(request.data.slots[0].width).to.equal(300); + expect(request.data.slots[0].height).to.equal(250); + }); + + it('should get size from bid.sizes too with legacy config mediaType', () => { + createLegacyVideoBidderRequestNoPlayer(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + const bidRequestCopy = clone(bidderRequest); + + let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + + expect(request.data.slots[0].width).to.equal(300); + expect(request.data.slots[0].height).to.equal(250); + }); + }); + + describe('hasVideoMediaType', () => { + it('should return true if mediaType is video and size_id is set', () => { + createVideoBidderRequest(); + const legacyVideoTypeBidRequest = spec.hasVideoMediaType(bidderRequest.bids[0]); + expect(legacyVideoTypeBidRequest).is.equal(true); + }); + + it('should return false if mediaType is video and size_id is not defined', () => { + expect(spec.hasVideoMediaType({ + bid: 99, + mediaType: 'video', + params: { + video: {} + } + })).is.equal(false); + }); + + it('should return false if bidRequest.mediaType is not equal to video', () => { + expect(spec.hasVideoMediaType({ + mediaType: 'banner' + })).is.equal(false); + }); + + it('should return false if bidRequest.mediaType is not defined', () => { + expect(spec.hasVideoMediaType({})).is.equal(false); + }); + + it('should return true if bidRequest.mediaTypes.video.context is instream and size_id is defined', () => { + expect(spec.hasVideoMediaType({ + mediaTypes: { + video: { + context: 'instream' + } + }, + params: { + video: { + size_id: 7 + } + } + })).is.equal(true); + }); - expect(post.slots[0].width).to.equal(300); - expect(post.slots[0].height).to.equal(250); + it('should return false if bidRequest.mediaTypes.video.context is instream but size_id is not defined', () => { + expect(spec.hasVideoMediaType({ + mediaTypes: { + video: { + context: 'instream' + } + }, + params: { + video: {} + } + })).is.equal(false); }); }); }); @@ -604,17 +1006,20 @@ describe('the rubicon adapter', () => { ] }; - let bids = spec.interpretResponse(response, { + let bids = spec.interpretResponse({body: response}, { bidRequest: bidderRequest.bids[0] }); expect(bids).to.be.lengthOf(2); - expect(bids[0].bidderCode).to.equal('rubicon'); expect(bids[0].width).to.equal(320); expect(bids[0].height).to.equal(50); expect(bids[0].cpm).to.equal(0.911); - expect(bids[0].creative_id).to.equal('crid-9'); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].netRevenue).to.equal(false); + expect(bids[0].rubicon.advertiserId).to.equal(7); + expect(bids[0].rubicon.networkId).to.equal(8); + expect(bids[0].creativeId).to.equal('crid-9'); expect(bids[0].currency).to.equal('USD'); expect(bids[0].ad).to.contain(`alert('foo')`) .and.to.contain(`<html>`) @@ -622,11 +1027,14 @@ describe('the rubicon adapter', () => { expect(bids[0].rubiconTargeting.rpfl_elemid).to.equal('/19968336/header-bid-tag-0'); expect(bids[0].rubiconTargeting.rpfl_14062).to.equal('43_tier_all_test'); - expect(bids[1].bidderCode).to.equal('rubicon'); expect(bids[1].width).to.equal(300); expect(bids[1].height).to.equal(250); expect(bids[1].cpm).to.equal(0.811); - expect(bids[1].creative_id).to.equal('crid-9'); + expect(bids[1].ttl).to.equal(300); + expect(bids[1].netRevenue).to.equal(false); + expect(bids[1].rubicon.advertiserId).to.equal(7); + expect(bids[1].rubicon.networkId).to.equal(8); + expect(bids[1].creativeId).to.equal('crid-9'); expect(bids[1].currency).to.equal('USD'); expect(bids[1].ad).to.contain(`alert('foo')`) .and.to.contain(`<html>`) @@ -654,7 +1062,7 @@ describe('the rubicon adapter', () => { }] }; - let bids = spec.interpretResponse(response, { + let bids = spec.interpretResponse({body: response}, { bidRequest: bidderRequest.bids[0] }); @@ -677,7 +1085,7 @@ describe('the rubicon adapter', () => { 'ads': [] }; - let bids = spec.interpretResponse(response, { + let bids = spec.interpretResponse({body: response}, { bidRequest: bidderRequest.bids[0] }); @@ -701,7 +1109,7 @@ describe('the rubicon adapter', () => { }] }; - let bids = spec.interpretResponse(response, { + let bids = spec.interpretResponse({body: response}, { bidRequest: bidderRequest.bids[0] }); @@ -711,7 +1119,7 @@ describe('the rubicon adapter', () => { it('should handle an error because of malformed json response', () => { let response = '{test{'; - let bids = spec.interpretResponse(response, { + let bids = spec.interpretResponse({body: response}, { bidRequest: bidderRequest.bids[0] }); @@ -752,40 +1160,46 @@ describe('the rubicon adapter', () => { 'account_id': 7780 }; - let bids = spec.interpretResponse(response, { + let bids = spec.interpretResponse({body: response}, { bidRequest: bidderRequest.bids[0] }); expect(bids).to.be.lengthOf(1); - expect(bids[0].bidderCode).to.equal('rubicon'); - expect(bids[0].creative_id).to.equal('crid-999999'); + expect(bids[0].creativeId).to.equal('crid-999999'); expect(bids[0].cpm).to.equal(1); - expect(bids[0].descriptionUrl).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); + 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].mediaType).to.equal('video'); + expect(bids[0].videoCacheKey).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); }); }); }); }); describe('user sync', () => { - const emilyUrl = 'https://tap-secure.rubiconproject.com/partner/scripts/rubicon/emily.html?rtb_ext=1'; + const emilyUrl = 'https://eus.rubiconproject.com/usync.html'; beforeEach(() => { resetUserSync(); }); it('should register the Emily iframe', () => { - let syncs = spec.getUserSyncs(); + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); expect(syncs).to.deep.equal({type: 'iframe', url: emilyUrl}); }); it('should not register the Emily iframe more than once', () => { - let syncs = spec.getUserSyncs(); + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); expect(syncs).to.deep.equal({type: 'iframe', url: emilyUrl}); // when called again, should still have only been called once diff --git a/test/spec/modules/rxrtbBidAdapter_spec.js b/test/spec/modules/rxrtbBidAdapter_spec.js new file mode 100644 index 00000000000..0785c6f144b --- /dev/null +++ b/test/spec/modules/rxrtbBidAdapter_spec.js @@ -0,0 +1,120 @@ +import {expect} from 'chai'; +import {spec} from 'modules/rxrtbBidAdapter'; + +describe('rxrtb adapater', () => { + describe('Test validate req', () => { + it('should accept minimum valid bid', () => { + let bid = { + bidder: 'rxrtb', + params: { + id: 89, + token: '658f11a5efbbce2f9be3f1f146fcbc22', + source: 'prebidtest' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(true); + }); + + it('should reject missing id', () => { + let bid = { + bidder: 'rxrtb', + params: { + token: '658f11a5efbbce2f9be3f1f146fcbc22', + source: 'prebidtest' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + + it('should reject id not Integer', () => { + let bid = { + bidder: 'rxrtb', + params: { + id: '123', + token: '658f11a5efbbce2f9be3f1f146fcbc22', + source: 'prebidtest' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + + it('should reject missing source', () => { + let bid = { + bidder: 'rxrtb', + params: { + id: 89, + token: '658f11a5efbbce2f9be3f1f146fcbc22' + } + }; + const isValid = spec.isBidRequestValid(bid); + + expect(isValid).to.equal(false); + }); + }); + + describe('Test build request', () => { + it('minimum request', () => { + let bid = { + bidder: 'rxrtb', + sizes: [[728, 90]], + bidId: '4d0a6829338a07', + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '20882439e3238c', + params: { + id: 89, + token: '658f11a5efbbce2f9be3f1f146fcbc22', + source: 'prebidtest' + }, + }; + const req = JSON.parse(spec.buildRequests([bid])[0].data); + + expect(req).to.have.property('id'); + expect(req).to.have.property('imp'); + expect(req).to.have.property('device'); + expect(req).to.have.property('site'); + expect(req).to.have.property('hb'); + expect(req.imp[0]).to.have.property('id'); + expect(req.imp[0]).to.have.property('banner'); + expect(req.device).to.have.property('ip'); + expect(req.device).to.have.property('ua'); + expect(req.site).to.have.property('id'); + expect(req.site).to.have.property('domain'); + }); + }); + + describe('Test interpret response', () => { + it('General banner response', () => { + let resp = spec.interpretResponse({ + body: { + id: 'abcd', + seatbid: [{ + bid: [{ + id: 'abcd', + impid: 'banner-bid', + price: 0.3, + w: 728, + h: 98, + adm: 'hello', + crid: 'efgh', + exp: 5 + }] + }] + } + }, null)[0]; + + expect(resp).to.have.property('requestId', 'banner-bid'); + expect(resp).to.have.property('cpm', 0.3); + expect(resp).to.have.property('width', 728); + expect(resp).to.have.property('height', 98); + expect(resp).to.have.property('creativeId', 'efgh'); + expect(resp).to.have.property('ttl', 5); + expect(resp).to.have.property('ad', 'hello'); + }); + }); +}); diff --git a/test/spec/modules/s2sTesting_spec.js b/test/spec/modules/s2sTesting_spec.js index f829087a967..33552011aa1 100644 --- a/test/spec/modules/s2sTesting_spec.js +++ b/test/spec/modules/s2sTesting_spec.js @@ -1,5 +1,6 @@ import { getSourceBidderMap, calculateBidSources, getSource } 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'); @@ -12,7 +13,7 @@ describe('s2sTesting', function () { let randomNumber = 0; beforeEach(() => { - mathRandomStub = sinon.stub(Math, 'random', () => { return randomNumber; }); + mathRandomStub = sinon.stub(Math, 'random').callsFake(() => { return randomNumber; }); }); afterEach(() => { @@ -307,131 +308,4 @@ describe('s2sTesting', function () { }); }); }); - - describe('addBidderSourceTargeting', () => { - const AST = CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING; - - function checkTargeting(bidder) { - var targeting = window.pbjs.bidderSettings[bidder][AST]; - var srcTargeting = targeting[targeting.length - 1]; - expect(srcTargeting.key).to.equal(`hb_source_${bidder}`); - expect(srcTargeting.val).to.be.a('function'); - expect(window.pbjs.bidderSettings[bidder].alwaysUseBid).to.be.true; - } - - function checkNoTargeting(bidder) { - var bs = window.pbjs.bidderSettings; - var targeting = bs[bidder] && bs[bidder][AST]; - if (!targeting) { - expect(targeting).to.be.undefined; - return; - } - expect(targeting.find((kvp) => { - return kvp.key === `hb_source_${bidder}`; - })).to.be.undefined; - } - - function checkTargetingVal(bidResponse, expectedVal) { - var targeting = window.pbjs.bidderSettings[bidResponse.bidderCode][AST]; - var targetingFunc = targeting[targeting.length - 1].val; - expect(targetingFunc(bidResponse)).to.equal(expectedVal); - } - - beforeEach(() => { - // set bidderSettings - window.pbjs.bidderSettings = {}; - }); - - it('should not set hb_source_<bidder> unless testing is on and includeSourceKvp is set', () => { - config.setConfig({s2sConfig: {bidders: ['rubicon', 'appnexus']}}); - expect(window.pbjs.bidderSettings).to.eql({}); - - config.setConfig({s2sConfig: {bidders: ['rubicon', 'appnexus'], testing: true}}); - expect(window.pbjs.bidderSettings).to.eql({}); - - config.setConfig({s2sConfig: { - bidders: ['rubicon', 'appnexus'], - testing: true, - bidderControl: { - rubicon: {bidSource: {server: 2, client: 1}}, - appnexus: {bidSource: {server: 1}} - } - }}); - expect(window.pbjs.bidderSettings).to.eql({}); - - config.setConfig({s2sConfig: { - bidders: ['rubicon', 'appnexus'], - testing: false, - bidderControl: { - rubicon: {includeSourceKvp: true}, - appnexus: {includeSourceKvp: true} - } - }}); - expect(window.pbjs.bidderSettings).to.eql({}); - }); - - it('should set hb_source_<bidder> if includeSourceKvp is set', () => { - config.setConfig({s2sConfig: { - bidders: ['rubicon', 'appnexus'], - testing: true, - bidderControl: { - rubicon: {includeSourceKvp: true}, - appnexus: {includeSourceKvp: true} - } - }}); - checkTargeting('rubicon'); - checkTargeting('appnexus'); - checkTargetingVal({bidderCode: 'rubicon', source: 'server'}, 'server'); - checkTargetingVal({bidderCode: 'appnexus', source: 'client'}, 'client'); - - // turn off appnexus - config.setConfig({s2sConfig: { - bidders: ['rubicon', 'appnexus'], - testing: true, - bidderControl: { - rubicon: {includeSourceKvp: true}, - appnexus: {includeSourceKvp: false} - } - }}); - checkTargeting('rubicon'); - checkNoTargeting('appnexus'); - checkTargetingVal({bidderCode: 'rubicon', source: 'client'}, 'client'); - - // should default to "client" - config.setConfig({s2sConfig: { - bidders: ['rubicon', 'appnexus'], - testing: true, - bidderControl: { - rubicon: {includeSourceKvp: true}, - appnexus: {includeSourceKvp: true} - } - }}); - checkTargeting('rubicon'); - checkTargeting('appnexus'); - checkTargetingVal({bidderCode: 'rubicon'}, 'client'); - checkTargetingVal({bidderCode: 'appnexus'}, 'client'); - }); - - it('should reset adServerTargeting when a new config is set', () => { - // set config with targeting - config.setConfig({s2sConfig: { - bidders: ['rubicon', 'appnexus'], - testing: true, - bidderControl: { - rubicon: {includeSourceKvp: true}, - appnexus: {includeSourceKvp: true} - } - }}); - checkTargeting('rubicon'); - checkTargeting('appnexus'); - - // set config without targeting - config.setConfig({s2sConfig: { - bidders: ['rubicon', 'appnexus'], - testing: true - }}); - checkNoTargeting('rubicon'); - checkNoTargeting('appnexus'); - }); - }); }); diff --git a/test/spec/modules/saraBidAdapter_spec.js b/test/spec/modules/saraBidAdapter_spec.js new file mode 100644 index 00000000000..1b5d75170ae --- /dev/null +++ b/test/spec/modules/saraBidAdapter_spec.js @@ -0,0 +1,293 @@ +import { expect } from 'chai'; +import { spec } from 'modules/saraBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('Sara Adapter', function () { + 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', () => { + let bid = { + 'bidder': 'sara', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + 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', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + function parseRequest(url) { + const res = {}; + url.split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + let bidRequests = [ + { + 'bidder': 'sara', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'sara', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'sara', + 'params': { + 'uid': '6' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', () => { + 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', '5'); + }); + + it('auids must not be duplicated', () => { + 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', '5,6'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', () => { + 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', '5,6'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', () => { + 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', '5,6'); + delete bidRequests[1].params.priceType; + }); + }); + + describe('interpretResponse', () => { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '<div>test content 1</div>', 'auid': 4, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '<div>test content 2</div>', 'auid': 5, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 6, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '<div>test content 4</div>', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', () => { + const bidRequests = [ + { + 'bidder': 'sara', + 'params': { + 'uid': '4' + }, + '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': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '<div>test content 1</div>', + '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', () => { + const bidRequests = [ + { + 'bidder': 'sara', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'sara', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'sara', + 'params': { + 'uid': '4' + }, + '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': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '<div>test content 1</div>', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '<div>test content 1</div>', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 5, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '<div>test content 2</div>', + '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', () => { + const bidRequests = [ + { + 'bidder': 'sara', + 'params': { + 'uid': '6' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'sara', + 'params': { + 'uid': '7' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'sara', + 'params': { + 'uid': '8' + }, + '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/sekindoUMBidAdapter_spec.js b/test/spec/modules/sekindoUMBidAdapter_spec.js index 073c09b0f4f..a5731da0789 100644 --- a/test/spec/modules/sekindoUMBidAdapter_spec.js +++ b/test/spec/modules/sekindoUMBidAdapter_spec.js @@ -1,78 +1,160 @@ -import {expect} from 'chai'; -import SekindoUMAdapter from '../../../modules/sekindoUMBidAdapter'; -var bidManager = require('src/bidmanager'); +import { expect } from 'chai'; +import { spec } from 'modules/sekindoUMBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; -describe('sekindoUM Adapter Tests', () => { - let _sekindoUMAdapter; - var addBidResponseSpy; +describe('sekindoUMAdapter', () => { + const adapter = newBidder(spec); - const bidderRequest = { - bidderCode: 'sekindoUM', - bids: [{ - bidder: 'sekindoUM', - bidId: 'sekindo_bidId', - bidderRequestId: 'sekindo_bidderRequestId', - requestId: 'sekindo_requestId', - placementCode: 'foo', - params: { - spaceId: 14071 - } - }] + const bannerParams = { + 'spaceId': '14071' }; - beforeEach(() => { - _sekindoUMAdapter = new SekindoUMAdapter(); + const videoParams = { + 'spaceId': '14071', + 'video': { + playerWidth: 300, + playerHeight: 250, + vid_vastType: 2 // optional + } + }; + + var bidRequests = { + 'bidder': 'sekindoUM', + 'params': bannerParams, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'mediaType': 'banner' + }; + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); }); - describe('sekindoUM callBids', () => { - beforeEach(() => { - _sekindoUMAdapter.callBids(bidderRequest); + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + bidRequests.mediaType = 'banner'; + bidRequests.params = bannerParams; + expect(spec.isBidRequestValid(bidRequests)).to.equal(true); }); - it('Verify sekindo script tag was created', () => { - var scriptTags = document.getElementsByTagName('script'); - var sekindoTagExists = 0; - for (var i = 0; i < scriptTags.length; i++) { - if (scriptTags[i].src.match('hb.sekindo.com') != null) { - sekindoTagExists = 1; - break; - } - } - expect(sekindoTagExists).to.equal(1); + it('should return false when required video params are missing', () => { + bidRequests.mediaType = 'video'; + bidRequests.params = bannerParams; + expect(spec.isBidRequestValid(bidRequests)).to.equal(false); + }); + + it('should return true when required Video params found', () => { + bidRequests.mediaType = 'video'; + bidRequests.params = videoParams; + expect(spec.isBidRequestValid(bidRequests)).to.equal(true); }); }); - describe('Should submit bid responses correctly', function () { - beforeEach(function () { - addBidResponseSpy = sinon.stub(bidManager, 'addBidResponse'); - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - _sekindoUMAdapter = new SekindoUMAdapter(); + describe('buildRequests', () => { + it('banner data should be a query string and method = GET', () => { + bidRequests.mediaType = 'banner'; + bidRequests.params = bannerParams; + const request = spec.buildRequests([bidRequests]); + expect(request[0].data).to.be.a('string'); + expect(request[0].method).to.equal('GET'); }); - afterEach(function () { - addBidResponseSpy.restore(); + it('video data should be a query string and method = GET', () => { + bidRequests.mediaType = 'video'; + bidRequests.params = videoParams; + const request = spec.buildRequests([bidRequests]); + expect(request[0].data).to.be.a('string'); + expect(request[0].method).to.equal('GET'); }); + }); - it('Should correctly submit valid bid to the bid manager', function () { - var HB_bid = { - adId: 'sekindoUM_bidId', - cpm: 0.23, - width: 300, - height: 250, - ad: '<h1>test ad</h1>' + describe('interpretResponse', () => { + it('banner should get correct bid response', () => { + let response = { + 'headers': function(header) { + return 'dummy header'; + }, + 'body': {'id': '30b31c1838de1e', 'bidderCode': 'sekindoUM', 'cpm': 2.1951, 'width': 300, 'height': 250, 'ad': '<h1>sekindo creative<\/h1>', 'ttl': 36000, 'creativeId': '323774', 'netRevenue': true, 'currency': 'USD'} }; - $$PREBID_GLOBAL$$.sekindoCB(bidderRequest.bids[0].bidId, HB_bid); - var firstBid = addBidResponseSpy.getCall(0).args[1]; - var placementCode1 = addBidResponseSpy.getCall(0).args[0]; + bidRequests.mediaType = 'banner'; + bidRequests.params = bannerParams; + let expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'bidderCode': 'sekindoUM', + 'cpm': 2.1951, + 'width': 300, + 'height': 250, + 'creativeId': '323774', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 36000, + 'ad': '<h1>sekindo creative</h1>' + } + ]; + let result = spec.interpretResponse(response, bidRequests); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('vastXml video should get correct bid response', () => { + let response = { + 'headers': function(header) { + return 'dummy header'; + }, + 'body': {'id': '30b31c1838de1e', 'bidderCode': 'sekindoUM', 'cpm': 2.1951, 'width': 300, 'height': 250, 'vastXml': '<vast/>', 'ttl': 36000, 'creativeId': '323774', 'netRevenue': true, 'currency': 'USD'} + }; - expect(firstBid.getStatusCode()).to.equal(1); - expect(firstBid.bidderCode).to.equal('sekindoUM'); - expect(firstBid.cpm).to.equal(0.23); - expect(firstBid.ad).to.equal('<h1>test ad</h1>'); - expect(placementCode1).to.equal('foo'); + bidRequests.mediaType = 'video'; + bidRequests.params = videoParams; + let expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'bidderCode': 'sekindoUM', + 'cpm': 2.1951, + 'width': 300, + 'height': 250, + 'creativeId': '323774', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 36000, + 'vastXml': '<vast/>' + } + ]; + let result = spec.interpretResponse(response, bidRequests); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); - expect(addBidResponseSpy.getCalls().length).to.equal(1); + it('vastUrl video should get correct bid response', () => { + let response = { + 'headers': function(header) { + return 'dummy header'; + }, + 'body': {'id': '30b31c1838de1e', 'bidderCode': 'sekindoUM', 'cpm': 2.1951, 'width': 300, 'height': 250, 'vastUrl': '//vastUrl', 'ttl': 36000, 'creativeId': '323774', 'netRevenue': true, 'currency': 'USD'} + }; + bidRequests.mediaType = 'video'; + bidRequests.params = videoParams; + let expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'bidderCode': 'sekindoUM', + 'cpm': 2.1951, + 'width': 300, + 'height': 250, + 'creativeId': '323774', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 36000, + 'vastUrl': '//vastUrl' + } + ]; + let result = spec.interpretResponse(response, bidRequests); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); }); }); }); diff --git a/test/spec/modules/serverbidBidAdapter_spec.js b/test/spec/modules/serverbidBidAdapter_spec.js index dcbd644b715..d3dc64ae6df 100644 --- a/test/spec/modules/serverbidBidAdapter_spec.js +++ b/test/spec/modules/serverbidBidAdapter_spec.js @@ -1,16 +1,16 @@ import { expect } from 'chai'; -import Adapter from 'modules/serverbidBidAdapter'; -import bidmanager from 'src/bidmanager'; -import * as utils from 'src/utils'; +import { spec } from 'modules/serverbidBidAdapter'; + +var bidFactory = require('src/bidfactory.js'); const ENDPOINT = 'https://e.serverbid.com/api/v2'; const SMARTSYNC_CALLBACK = 'serverbidCallBids'; const REQUEST = { 'bidderCode': 'serverbid', - 'requestId': 'a4713c32-3762-4798-b342-4ab810ca770d', + 'auctionId': 'a4713c32-3762-4798-b342-4ab810ca770d', 'bidderRequestId': '109f2a181342a9', - 'bids': [{ + 'bidRequest': [{ 'bidder': 'serverbid', 'params': { 'networkId': 9969, @@ -23,7 +23,22 @@ const REQUEST = { ], 'bidId': '2b0f82502298c9', 'bidderRequestId': '109f2a181342a9', - 'requestId': 'a4713c32-3762-4798-b342-4ab810ca770d' + 'auctionId': 'a4713c32-3762-4798-b342-4ab810ca770d' + }, + { + 'bidder': 'serverbid', + 'params': { + 'networkId': 9969, + 'siteId': 730181 + }, + 'placementCode': 'div-gpt-ad-1487778092495-0', + 'sizes': [ + [728, 90], + [970, 90] + ], + 'bidId': '123', + 'bidderRequestId': '109f2a181342a9', + 'auctionId': 'a4713c32-3762-4798-b342-4ab810ca770d' }], 'start': 1487883186070, 'auctionStart': 1487883186069, @@ -31,203 +46,209 @@ const REQUEST = { }; const RESPONSE = { - 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, - 'decisions': { - '2b0f82502298c9': { - 'adId': 2364764, - 'creativeId': 1950991, - 'flightId': 2788300, - 'campaignId': 542982, - 'clickUrl': 'https://e.serverbid.com/r', - 'impressionUrl': 'https://e.serverbid.com/i.gif', - 'contents': [{ - 'type': 'html', - 'body': '<html></html>', - 'data': { - 'height': 90, - 'width': 728, - 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', - 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' - }, - 'template': 'image' - }], - 'height': 90, - 'width': 728, - 'events': [], - 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5} - }, + 'headers': null, + 'body': { + 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, + 'decisions': { + '2b0f82502298c9': { + 'adId': 2364764, + 'creativeId': 1950991, + 'flightId': 2788300, + 'campaignId': 542982, + 'clickUrl': 'https://e.serverbid.com/r', + 'impressionUrl': 'https://e.serverbid.com/i.gif', + 'contents': [{ + 'type': 'html', + 'body': '<html></html>', + 'data': { + 'height': 90, + 'width': 728, + 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', + 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' + }, + 'template': 'image' + }], + 'height': 90, + 'width': 728, + 'events': [], + 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5} + }, + '123': { + 'adId': 2364764, + 'creativeId': 1950991, + 'flightId': 2788300, + 'campaignId': 542982, + 'clickUrl': 'https://e.serverbid.com/r', + 'impressionUrl': 'https://e.serverbid.com/i.gif', + 'contents': [{ + 'type': 'html', + 'body': '<html></html>', + 'data': { + 'height': 90, + 'width': 728, + 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', + 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' + }, + 'template': 'image' + }], + 'height': 90, + 'width': 728, + 'events': [], + 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5} + } + } } }; -describe('serverbidAdapter', () => { - let adapter; - - beforeEach(() => adapter = new Adapter()); - - describe('request function', () => { - let xhr; - let requests; - let pbConfig; +describe('Serverbid BidAdapter', () => { + let bidRequests; + let adapter = spec; + + beforeEach(() => { + bidRequests = [ + { + bidder: 'serverbid', + params: { + networkId: '9969', + siteId: '730181' + }, + placementCode: 'header-bid-tag-1', + sizes: [[300, 250], [300, 600]], + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + }); - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - pbConfig = REQUEST; - // just a single slot - pbConfig.bids = [pbConfig.bids[0]]; + describe('bid request validation', () => { + it('should accept valid bid requests', () => { + let bid = { + bidder: 'serverbid', + params: { + networkId: '9969', + siteId: '123' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - afterEach(() => xhr.restore()); + it('should accept valid bid requests with extra fields', () => { + let bid = { + bidder: 'serverbid', + params: { + networkId: '9969', + siteId: '123', + zoneId: '123' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); + it('should reject bid requests without siteId', () => { + let bid = { + bidder: 'serverbid', + params: { + networkId: '9969' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('requires paramaters to make request', () => { - adapter.callBids({}); - expect(requests).to.be.empty; + it('should reject bid requests without networkId', () => { + let bid = { + bidder: 'serverbid', + params: { + siteId: '9969' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); + }); - it('requires networkId and siteId', () => { - let backup = pbConfig.bids[0].params; - pbConfig.bids[0].params = { networkId: 1234 }; // no hbid - adapter.callBids(pbConfig); - expect(requests).to.be.empty; + describe('buildRequests validation', () => { + it('creates request data', () => { + let request = spec.buildRequests(bidRequests); - pbConfig.bids[0].params = { siteId: 1234 }; // no placementid - adapter.callBids(pbConfig); - expect(requests).to.be.empty; + expect(request).to.exist.and.to.be.a('object'); + }); + + it('request to serverbid should contain a url', () => { + let request = spec.buildRequests(bidRequests); - pbConfig.bids[0].params = backup; + expect(request.url).to.have.string('serverbid.com'); }); - it('sends bid request to ENDPOINT via POST', () => { - adapter.callBids(pbConfig); - expect(requests[0].url).to.equal(ENDPOINT); - expect(requests[0].method).to.equal('POST'); + it('requires valid bids to make request', () => { + let request = spec.buildRequests([]); + expect(request.bidRequest).to.be.empty; }); - }); - describe('response handler', () => { - let server; + it('sends bid request to ENDPOINT via POST', () => { + let request = spec.buildRequests(bidRequests); - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); - sinon.stub(utils, 'getBidRequest').returns(REQUEST); + expect(request.method).to.have.string('POST'); }); + }); + describe('interpretResponse validation', () => { + it('response should have valid bidderCode', () => { + let bidRequest = spec.buildRequests(REQUEST.bidRequest); + let bid = bidFactory.createBid(1, bidRequest.bidRequest[0]); - afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); - utils.getBidRequest.restore(); + expect(bid.bidderCode).to.equal('serverbid'); }); - it('registers bids', () => { - server.respondWith(JSON.stringify(RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('response should include objects for all bids', () => { + let bids = spec.interpretResponse(RESPONSE, REQUEST); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('cpm'); - expect(response.cpm).to.be.above(0); + expect(bids.length).to.equal(2); }); - describe('with SMARTSYNC=true', () => { - it('registers bids when callback is called promptly by smartsync', (done) => { - window.SMARTSYNC = true; - server.respondWith(JSON.stringify(RESPONSE)); - - adapter.callBids(REQUEST); - - setTimeout(() => { - window[SMARTSYNC_CALLBACK](); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('cpm'); - expect(response.cpm).to.be.above(0); - window.SMARTSYNC = false; - done(); - }, 0); + it('registers bids', () => { + let bids = spec.interpretResponse(RESPONSE, REQUEST); + bids.forEach(b => { + expect(b).to.have.property('cpm'); + expect(b.cpm).to.be.above(0); + expect(b).to.have.property('requestId'); + expect(b).to.have.property('cpm'); + expect(b).to.have.property('width'); + expect(b).to.have.property('height'); + expect(b).to.have.property('ad'); + expect(b).to.have.property('currency', 'USD'); + expect(b).to.have.property('creativeId'); + expect(b).to.have.property('ttl', 360); + expect(b).to.have.property('netRevenue', true); + expect(b).to.have.property('referrer'); }); + }); - it('registers bids when callback is never called by smartsync', (done) => { - window.SMARTSYNC = true; - window.SMARTSYNC_TIMEOUT = 100; - - server.respondWith(JSON.stringify(RESPONSE)); + it('handles nobid responses', () => { + let EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {'decisions': null}}) + let bids = spec.interpretResponse(EMPTY_RESP, REQUEST); - adapter.callBids(REQUEST); - setTimeout(() => { - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + expect(bids).to.be.empty; + }); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('cpm'); - expect(response.cpm).to.be.above(0); - window.SMARTSYNC = false; - done(); - }, window.SMARTSYNC_TIMEOUT * 2); - }); + it('handles no server response', () => { + let bids = spec.interpretResponse(null, REQUEST); - it('registers bids when callback is called (but late) by smartsync', (done) => { - window.SMARTSYNC = true; - window.SMARTSYNC_TIMEOUT = 100; - - server.respondWith(JSON.stringify(RESPONSE)); - - adapter.callBids(REQUEST); - setTimeout(() => { - window[SMARTSYNC_CALLBACK](); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('cpm'); - expect(response.cpm).to.be.above(0); - window.SMARTSYNC = false; - done(); - }, window.SMARTSYNC_TIMEOUT * 2); - }); + expect(bids).to.be.empty; }); + }); + describe('getUserSyncs', () => { + let syncOptions = {'iframeEnabled': true}; - it('handles nobid responses', () => { - server.respondWith(JSON.stringify({ - 'decisions': [] - })); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); - }); + it('handles empty sync options', () => { + let opts = spec.getUserSyncs({}); - it('handles JSON.parse errors', () => { - server.respondWith(''); + expect(opts).to.be.empty; + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('should return a sync url if iframe syncs are enabled', () => { + let opts = spec.getUserSyncs(syncOptions); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); + expect(opts.length).to.equal(1); }); }); }); diff --git a/test/spec/modules/serverbidServerBidAdapter_spec.js b/test/spec/modules/serverbidServerBidAdapter_spec.js new file mode 100644 index 00000000000..29d35b921d6 --- /dev/null +++ b/test/spec/modules/serverbidServerBidAdapter_spec.js @@ -0,0 +1,299 @@ +import { expect } from 'chai'; +import Adapter from 'modules/serverbidServerBidAdapter'; +import * as utils from 'src/utils'; +import { config } from 'src/config'; +import { ajax } from 'src/ajax'; + +const ENDPOINT = 'https://e.serverbid.com/api/v2'; + +let CONFIG = { + enabled: true, + bidders: ['appnexus'], + timeout: 1000, + adapter: 'serverbidServer', + networkId: 9969, + siteId: 730181, + endpoint: ENDPOINT +}; + +let CONFIG_ARG = { + s2sConfig: CONFIG +} + +const REQUEST = { + 'account_id': '1', + 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', + 'max_bids': 1, + 'timeout_millis': 1000, + 'url': '', + 'prebid_version': '0.21.0-pre', + 'ad_units': [ + { + 'code': 'div-gpt-ad-1460505748561-0', + 'sizes': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ], + 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', + 'bids': [ + { + 'bid_id': '123', + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394', + 'member': 123 + } + } + ] + } + ] +}; + +const BID_REQUESTS = [ + { + 'bidderCode': 'appnexus', + 'auctionId': '173afb6d132ba3', + 'bidderRequestId': '3d1063078dfcc8', + 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394', + 'member': 123 + }, + 'bid_id': '123', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', + 'sizes': [ + { + 'w': 300, + 'h': 250 + } + ], + 'bidId': '259fb43aaa06c1', + 'bidderRequestId': '3d1063078dfcc8', + 'auctionId': '173afb6d132ba3' + } + ], + 'auctionStart': 1510852447530, + 'timeout': 5000, + 'src': 's2s', + 'doneCbCallCount': 0 + } +]; + +const RESPONSE = { + 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, + 'decisions': { + '123': [{ + 'adId': 2364764, + 'creativeId': 1950991, + 'flightId': 2788300, + 'campaignId': 542982, + 'clickUrl': 'https://e.serverbid.com/r', + 'impressionUrl': 'https://e.serverbid.com/i.gif', + 'contents': [{ + 'type': 'html', + 'body': '<html></html>', + 'data': { + 'height': 300, + 'width': 250, + 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', + 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' + }, + 'template': 'image' + }], + 'height': 250, + 'width': 300, + 'events': [], + 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5} + }], + } +}; + +const RESPONSE_NO_BID_NO_UNIT = { + 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, + 'decisions': { + '123': [] + } +}; + +const REQUEST_TWO_UNITS = { + 'account_id': '1', + 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', + 'max_bids': 1, + 'timeout_millis': 1000, + 'url': '', + 'prebid_version': '0.21.0-pre', + 'ad_units': [ + { + 'code': 'div-gpt-ad-1460505748561-0', + 'sizes': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ], + 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', + 'bids': [ + { + 'bid_id': '123', + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394', + 'member': 123 + } + } + ] + }, + { + 'code': 'div-gpt-ad-1460505748561-1', + 'sizes': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ], + 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786bb86d', + 'bids': [ + { + 'bid_id': '101111', + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394', + 'member': 123 + } + } + ] + } + ] +}; + +describe('ServerBid S2S Adapter', () => { + let adapter, + addBidResponse = sinon.spy(), + done = sinon.spy(); + + beforeEach(() => adapter = new Adapter()); + + afterEach(() => { + addBidResponse.resetHistory(); + done.resetHistory(); + }); + + describe('request function', () => { + let xhr; + let requests; + + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + }); + + afterEach(() => xhr.restore()); + + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('response handler', () => { + let server; + + beforeEach(() => { + server = sinon.fakeServer.create(); + sinon.stub(utils, 'getBidRequest').returns({ + bidId: '123' + }); + }); + + afterEach(() => { + server.restore(); + utils.getBidRequest.restore(); + }); + + it('registers bids', () => { + server.respondWith(JSON.stringify(RESPONSE)); + + config.setConfig(CONFIG_ARG); + 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('cpm', 0.5); + expect(response).to.have.property('adId', '123'); + }); + + it('registers no-bid response when ad unit not set', () => { + server.respondWith(JSON.stringify(RESPONSE_NO_BID_NO_UNIT)); + + config.setConfig(CONFIG_ARG); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + sinon.assert.calledOnce(addBidResponse); + + const ad_unit_code = addBidResponse.firstCall.args[0]; + expect(ad_unit_code).to.equal('div-gpt-ad-1460505748561-0'); + + const response = addBidResponse.firstCall.args[1]; + 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'); + }); + + it('registers no-bid response when ad unit is set', () => { + server.respondWith(JSON.stringify(RESPONSE_NO_BID_NO_UNIT)); + + config.setConfig(CONFIG_ARG); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + sinon.assert.calledOnce(addBidResponse); + + const ad_unit_code = addBidResponse.firstCall.args[0]; + expect(ad_unit_code).to.equal('div-gpt-ad-1460505748561-0'); + + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('statusMessage', 'Bid returned empty or error response'); + }); + + it('registers no-bid response when there are less bids than requests', () => { + server.respondWith(JSON.stringify(RESPONSE)); + + config.setConfig(CONFIG_ARG); + adapter.callBids(REQUEST_TWO_UNITS, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + + sinon.assert.calledTwice(addBidResponse); + + 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('statusMessage', 'Bid available'); + expect(addBidResponse.secondCall.args[1]) + .to.have.property('statusMessage', 'Bid returned empty or error response'); + }); + }); +}); diff --git a/test/spec/modules/sharethroughAnalyticsAdapter_spec.js b/test/spec/modules/sharethroughAnalyticsAdapter_spec.js deleted file mode 100644 index 8968e0461fb..00000000000 --- a/test/spec/modules/sharethroughAnalyticsAdapter_spec.js +++ /dev/null @@ -1,90 +0,0 @@ -import sharethroughAnalytics from 'modules/sharethroughAnalyticsAdapter'; -import { expect } from 'chai'; - -describe('sharethrough analytics adapter', () => { - let sandbox; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('track', () => { - describe('when event type is bidRequested', () => { - beforeEach(() => { - let eventType = 'bidRequested'; - let args = {'bidderCode': 'sharethrough', 'bids': {'0': {'placementCode': 'fake placement Code'}}}; - sharethroughAnalytics.track({eventType, args}) - }); - - it('placementCodeSet contains a value', () => { - expect(sharethroughAnalytics.placementCodeSet['fake placement Code'] == undefined).to.equal(false) - }); - }); - }); - - describe('bid won handler', () => { - let fireLoseBeaconStub; - - beforeEach(() => { - fireLoseBeaconStub = sandbox.stub(sharethroughAnalytics, 'fireLoseBeacon'); - }); - - describe('when bidderCode is not sharethrough and sharethrough is in bid', () => { - beforeEach(() => { - sharethroughAnalytics.placementCodeSet['div-gpt-ad-1460505748561-0'] = {'adserverRequestId': '0eca470d-fcac-48e6-845a-c86483ccaa0c'} - var args = { - 'bidderCode': 'someoneelse', - 'width': 600, - 'height': 300, - 'statusMessage': 'Bid available', - 'adId': '23fbe93a90c924', - 'cpm': 3.984986853301525, - 'adserverRequestId': '0eca470d-fcac-48e6-845a-c86483ccaa0c', - 'winId': '1c404469-f7bb-4e50-b6f6-a8eaf0808999', - 'pkey': 'xKcxTTHyndFyVx7T8GKSzxPE', - 'ad': '<div></div>', - 'requestId': 'dd2420bd-cdc2-4c66-8479-f3499ece73da', - 'responseTimestamp': 1473983655565, - 'requestTimestamp': 1473983655458, - 'bidder': 'sharethrough', - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'timeToRespond': 107, - 'pbLg': '3.50', - 'pbMg': '3.90', - 'pbHg': '3.98', - 'pbAg': '3.95', - 'pbDg': '3.95', - 'size': '600x300', - 'adserverTargeting': { - 'hb_bidder': 'sharethrough', - 'hb_adid': '23fbe93a90c924', - 'hb_pb': '3.90', - 'hb_size': '600x300' - } - }; - - sharethroughAnalytics.bidWon(args); - }); - - it('should fire lose beacon', () => { - sinon.assert.calledOnce(fireLoseBeaconStub); - }); - }); - }); - - describe('lose beacon is fired', () => { - beforeEach(() => { - sandbox.stub(sharethroughAnalytics, 'fireBeacon'); - sharethroughAnalytics.fireLoseBeacon('someoneelse', 10.0, 'arid', 'losebeacontype'); - }); - - it('should call correct url', () => { - let winUrl = sharethroughAnalytics.fireBeacon.firstCall.args[0]; - expect(winUrl).to.contain(sharethroughAnalytics.STR_BEACON_HOST + 'winnerBidderCode=someoneelse&winnerCpm=10&arid=arid&type=losebeacontype&hbVersion=%24prebid.version%24&strVersion=0.1.0&hbSource=prebid&'); - }); - }); -}); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index b92dfe4d493..aa9477d2557 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -1,239 +1,179 @@ import { expect } from 'chai'; -import Adapter from '../../../modules/sharethroughBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import bidfactory from '../../../src/bidfactory'; - -describe('sharethrough adapter', () => { - let adapter; - let sandbox; - let bidsRequestedOriginal; - - const bidderRequest = { - bidderCode: 'sharethrough', - bids: [ - { - bidder: 'sharethrough', - bidId: 'bidId1', - sizes: [[600, 300]], - placementCode: 'foo', - params: { - pkey: 'aaaa1111' - } - }, - { - bidder: 'sharethrough', - bidId: 'bidId2', - sizes: [[700, 400]], - placementCode: 'bar', - params: { - pkey: 'bbbb2222' - } +import { sharethroughAdapterSpec } from 'modules/sharethroughBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const spec = newBidder(sharethroughAdapterSpec).getSpec(); +const bidderRequest = [ + { + bidder: 'sharethrough', + bidId: 'bidId1', + sizes: [[600, 300]], + placementCode: 'foo', + params: { + pkey: 'aaaa1111' + } + }, + { + bidder: 'sharethrough', + bidId: 'bidId2', + sizes: [[700, 400]], + placementCode: 'bar', + params: { + pkey: 'bbbb2222' + } + }]; +const prebidRequest = [{ + method: 'GET', + url: document.location.protocol + '//btlr.sharethrough.com' + '/header-bid/v1', + data: { + bidId: 'bidId', + placement_key: 'pKey' + } +}]; +const bidderResponse = { + body: { + 'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994', + 'bidId': 'bidId1', + 'version': 1, + 'creatives': [{ + 'auctionWinId': 'b2882d5e-bf8b-44da-a91c-0c11287b8051', + 'cpm': 12.34, + 'creative': { + 'deal_id': 'aDealId', + 'creative_key': 'aCreativeId', + 'title': '✓ à la mode' } - ] - }; - - beforeEach(() => { - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; - $$PREBID_GLOBAL$$._bidsRequested = []; - }); - - afterEach(() => { - sandbox.restore(); - - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; - }); - - describe('callBids', () => { - let firstBidUrl; - let secondBidUrl; - - beforeEach(() => { - sandbox.spy(adapter.str, 'ajax'); - }); - - it('should call ajax to make a request for each bid', () => { - adapter.callBids(bidderRequest); - - firstBidUrl = adapter.str.ajax.firstCall.args[0]; - secondBidUrl = adapter.str.ajax.secondCall.args[0]; - - sinon.assert.calledTwice(adapter.str.ajax); - - expect(firstBidUrl).to.contain(adapter.str.STR_BTLR_HOST + '/header-bid/v1?bidId=bidId1&placement_key=aaaa1111&hbVersion=%24prebid.version%24&strVersion=1.2.0&hbSource=prebid&'); - expect(secondBidUrl).to.contain(adapter.str.STR_BTLR_HOST + '/header-bid/v1?bidId=bidId2&placement_key=bbbb2222&hbVersion=%24prebid.version%24&strVersion=1.2.0&hbSource=prebid&'); + }], + 'stxUserId': '' + }, + header: { get: (header) => header } +}; +// Mirrors the one in modules/sharethroughBidAdapter.js as the function is unexported +const b64EncodeUnicode = (str) => { + return btoa( + encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, + function toSolidBytes(match, p1) { + return String.fromCharCode('0x' + p1); + })); +} +describe('sharethrough adapter spec', () => { + describe('.code', () => { + it('should return a bidder code of sharethrough', () => { + expect(spec.code).to.eql('sharethrough'); }); - }); - - describe('bid requests', () => { - let firstBid; - let secondBid; - let server; - let stubAddBidResponse; - let stubCreateBid; - - beforeEach(() => { - stubAddBidResponse = sandbox.stub(bidManager, 'addBidResponse'); - server = sinon.fakeServer.create(); - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter.str.placementCodeSet['foo'] = {}; - adapter.str.placementCodeSet['bar'] = {}; - // respond - - let bidderResponse1 = { - 'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994', - 'bidId': 'bidId1', - 'creatives': [ - { - 'cpm': 12.34, - 'auctionWinId': 'b2882d5e-bf8b-44da-a91c-0c11287b8051', - 'version': 1 - } - ], - 'stxUserId': '' - }; + }) - let bidderResponse2 = { - 'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994', - 'bidId': 'bidId2', - 'creatives': [ - { - 'cpm': 12.35, - 'auctionWinId': 'b2882d5e-bf8b-44da-a91c-0c11287b8051', - 'version': 1 - } - ], - 'stxUserId': '' + describe('.isBidRequestValid', () => { + it('should return false if req has no pkey', () => { + const invalidBidRequest = { + bidder: 'sharethrough', + params: { + notPKey: 'abc123' + } }; - - server.respondWith(/aaaa1111/, JSON.stringify(bidderResponse1)); - server.respondWith(/bbbb2222/, JSON.stringify(bidderResponse2)); - adapter.callBids(bidderRequest); - - server.respond(); - - firstBid = bidManager.addBidResponse.firstCall.args[1]; - secondBid = bidManager.addBidResponse.secondCall.args[1]; - }); - - afterEach(() => { - server.restore(); + expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); - it('should add a bid object for each bid', () => { - sinon.assert.calledTwice(bidManager.addBidResponse); + it('should return false if req has wrong bidder code', () => { + const invalidBidRequest = { + bidder: 'notSharethrough', + params: { + notPKey: 'abc123' + } + }; + expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); - it('should pass the correct placement code as first param', () => { - let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; - let secondPlacementCode = bidManager.addBidResponse.secondCall.args[0]; + it('should return true if req is correct', () => { + expect(spec.isBidRequestValid(bidderRequest[0])).to.eq(true); + expect(spec.isBidRequestValid(bidderRequest[1])).to.eq(true); + }) + }); - expect(firstPlacementCode).to.eql('foo'); - expect(secondPlacementCode).to.eql('bar'); - }); + describe('.buildRequests', () => { + it('should return an array of requests', () => { + const bidRequests = spec.buildRequests(bidderRequest); - it('should include the bid request bidId as the adId', () => { - expect(firstBid).to.have.property('adId', 'bidId1'); - expect(secondBid).to.have.property('adId', 'bidId2'); + expect(bidRequests[0].url).to.eq( + 'http://btlr.sharethrough.com/header-bid/v1'); + expect(bidRequests[1].url).to.eq( + 'http://btlr.sharethrough.com/header-bid/v1') + expect(bidRequests[0].method).to.eq('GET'); }); - it('should have a good statusCode', () => { - expect(firstBid.getStatusCode()).to.eql(1); - expect(secondBid.getStatusCode()).to.eql(1); + it('should add consent parameters if gdprConsent is present', () => { + const gdprConsent = { consentString: 'consent_string123', gdprApplies: true }; + const fakeBidRequest = { gdprConsent: gdprConsent }; + const bidRequest = spec.buildRequests(bidderRequest, fakeBidRequest)[0]; + expect(bidRequest.data.consent_string).to.eq('consent_string123'); + expect(bidRequest.data.consent_required).to.eq(true); }); + }); - it('should add the CPM to the bid object', () => { - expect(firstBid).to.have.property('cpm', 12.34); - expect(secondBid).to.have.property('cpm', 12.35); + describe('.interpretResponse', () => { + it('returns a correctly parsed out response', () => { + expect(spec.interpretResponse(bidderResponse, prebidRequest[0])[0]).to.include( + { + width: 0, + height: 0, + cpm: 12.34, + creativeId: 'aCreativeId', + deal_id: 'aDealId', + currency: 'USD', + netRevenue: true, + ttl: 360, + }); }); - it('should add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'sharethrough'); - expect(secondBid).to.have.property('bidderCode', 'sharethrough'); + it('returns a blank array if there are no creatives', () => { + const bidResponse = { body: { creatives: [] } }; + expect(spec.interpretResponse(bidResponse, prebidRequest[0])).to.be.an('array').that.is.empty; }); - it('should include the ad on the bid object', () => { - expect(firstBid).to.have.property('ad'); - expect(secondBid).to.have.property('ad'); + it('returns a blank array if body object is empty', () => { + const bidResponse = { body: {} }; + expect(spec.interpretResponse(bidResponse, prebidRequest[0])).to.be.an('array').that.is.empty; }); - it('should include the size on the bid object', () => { - expect(firstBid).to.have.property('width', 600); - expect(firstBid).to.have.property('height', 300); - expect(secondBid).to.have.property('width', 700); - expect(secondBid).to.have.property('height', 400); + it('returns a blank array if body is null', () => { + const bidResponse = { body: null }; + expect(spec.interpretResponse(bidResponse, prebidRequest[0])).to.be.an('array').that.is.empty; }); - it('should include the pkey', () => { - expect(firstBid).to.have.property('pkey', 'aaaa1111'); - expect(secondBid).to.have.property('pkey', 'bbbb2222'); + it('correctly sends back a sfp script tag', () => { + const adMarkup = spec.interpretResponse(bidderResponse, prebidRequest[0])[0].ad; + let resp = null; + + expect(() => btoa(JSON.stringify(bidderResponse))).to.throw(); + expect(() => resp = b64EncodeUnicode(JSON.stringify(bidderResponse))).not.to.throw(); + expect(adMarkup).to.match( + /data-str-native-key="pKey" data-stx-response-name=\"str_response_bidId\"/); + expect(!!adMarkup.indexOf(resp)).to.eql(true); + expect(adMarkup).to.match( + /<script src="\/\/native.sharethrough.com\/assets\/sfp-set-targeting.js"><\/script>/); + expect(adMarkup).to.match( + /sfp_js.src = "\/\/native.sharethrough.com\/assets\/sfp.js";/); + expect(adMarkup).to.match( + /window.top.document.getElementsByTagName\('body'\)\[0\].appendChild\(sfp_js\);/) }); + }); - describe('when bidResponse string cannot be JSON parsed', () => { - beforeEach(() => { - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter.str.placementCodeSet['foo'] = {}; - - server.respondWith(/aaaa1111/, 'non JSON string'); - adapter.callBids(bidderRequest); - - server.respond(); - }); - - afterEach(() => { - server.restore(); - stubAddBidResponse.reset(); - }); - - it('should add a bid response', () => { - sinon.assert.called(bidManager.addBidResponse); - }); - - it('should set bidder code on invalid bid response', () => { - let bidResponse = bidManager.addBidResponse.firstCall.args[1] - expect(bidResponse).to.have.property('bidderCode', 'sharethrough') - }); + describe('.getUserSyncs', () => { + const cookieSyncs = ['cookieUrl1', 'cookieUrl2', 'cookieUrl3']; + const serverResponses = [{ body: { cookieSyncUrls: cookieSyncs } }]; + + it('returns an array of correctly formatted user syncs', () => { + const syncArray = spec.getUserSyncs({ pixelEnabled: true }, serverResponses); + expect(syncArray).to.deep.equal([ + { type: 'image', url: 'cookieUrl1' }, + { type: 'image', url: 'cookieUrl2' }, + { type: 'image', url: 'cookieUrl3' }] + ); }); - describe('when no fill', () => { - beforeEach(() => { - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - adapter.str.placementCodeSet['foo'] = {}; - - let bidderResponse1 = { - 'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994', - 'bidId': 'bidId1', - 'creatives': [ - { - 'cpm': 12.34, - 'auctionWinId': 'b2882d5e-bf8b-44da-a91c-0c11287b8051', - 'version': 1 - } - ], - 'stxUserId': '' - }; - - server.respondWith(/aaaa1111/, JSON.stringify(bidderResponse1)); - adapter.callBids(bidderRequest); - - server.respond(); - }); - - afterEach(() => { - server.restore(); - stubAddBidResponse.reset(); - }); - - it('should add a bid response', () => { - sinon.assert.called(bidManager.addBidResponse); - }); - - it('should set bidder code on invalid bid response', () => { - let bidResponse = bidManager.addBidResponse.firstCall.args[1] - expect(bidResponse).to.have.property('bidderCode', 'sharethrough') - }); + it('returns an empty array if pixels are not enabled', () => { + const syncArray = spec.getUserSyncs({ pixelEnabled: false }, serverResponses); + expect(syncArray).to.be.an('array').that.is.empty; }); }); }); diff --git a/test/spec/modules/sigmoidAnalyticsAdapter_spec.js b/test/spec/modules/sigmoidAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..115c296d489 --- /dev/null +++ b/test/spec/modules/sigmoidAnalyticsAdapter_spec.js @@ -0,0 +1,67 @@ +import sigmoidAnalytic from 'modules/sigmoidAnalyticsAdapter'; +import { expect } from 'chai'; +let events = require('src/events'); +let adaptermanager = require('src/adaptermanager'); +let constants = require('src/constants.json'); + +describe('sigmoid Prebid Analytic', function () { + let xhr; + before(() => { + xhr = sinon.useFakeXMLHttpRequest(); + }) + after(() => { + sigmoidAnalytic.disableAnalytics(); + xhr.restore(); + }); + + describe('enableAnalytics', function () { + beforeEach(() => { + sinon.spy(sigmoidAnalytic, 'track'); + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(() => { + sigmoidAnalytic.track.restore(); + events.getEvents.restore(); + }); + it('should catch all events', function () { + adaptermanager.registerAnalyticsAdapter({ + code: 'sigmoid', + adapter: sigmoidAnalytic + }); + + adaptermanager.enableAnalytics({ + provider: 'sigmoid', + options: { + publisherIds: ['test_sigmoid_prebid_analytid_publisher_id'] + } + }); + + 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(sigmoidAnalytic.track, 5); + }); + }); + describe('build utm tag data', () => { + beforeEach(() => { + localStorage.setItem('sigmoid_analytics_utm_source', 'utm_source'); + localStorage.setItem('sigmoid_analytics_utm_medium', 'utm_medium'); + localStorage.setItem('sigmoid_analytics_utm_campaign', ''); + localStorage.setItem('sigmoid_analytics_utm_term', ''); + localStorage.setItem('sigmoid_analytics_utm_content', ''); + localStorage.setItem('sigmoid_analytics_utm_timeout', Date.now()); + }); + it('should build utm data from local storage', () => { + let utmTagData = sigmoidAnalytic.buildUtmTagData(); + expect(utmTagData.utm_source).to.equal('utm_source'); + expect(utmTagData.utm_medium).to.equal('utm_medium'); + expect(utmTagData.utm_campaign).to.equal(''); + expect(utmTagData.utm_term).to.equal(''); + expect(utmTagData.utm_content).to.equal(''); + }); + }); +}); diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index bfb89cf4ead..82c2098f234 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -1,154 +1,201 @@ -describe('smartadserver adapter tests', function () { - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - var adapter = require('modules/smartadserverBidAdapter'); - var adLoader = require('src/adloader'); - var expect = require('chai').expect; - var bidmanager = require('src/bidmanager'); - var CONSTANTS = require('src/constants.json'); - - var DEFAULT_PARAMS = { - bidderCode: 'smartadserver', - bids: [{ - bidId: 'abcd1234', - sizes: [[300, 250], [300, 200]], - bidder: 'smartadserver', - params: { - domain: 'http://www.smartadserver.com', - siteId: '1234', - pageId: '5678', - formatId: '90', - target: 'test=prebid', - currency: 'EUR', - bidfloor: 0.420 - }, - requestId: 'efgh5678', - placementCode: 'sas_42' - } - ] - }; - - var DEFAULT_PARAMS_WO_OPTIONAL = { - bidderCode: 'smartadserver', - bids: [{ - bidId: 'abcd1234', - sizes: [[300, 250], [300, 200]], - bidder: 'smartadserver', - params: { - domain: 'http://www.smartadserver.com', - siteId: '1234', - pageId: '5678', - formatId: '90' - }, - requestId: 'efgh5678', - placementCode: 'sas_42' - } - ] - }; +import { + expect +} from 'chai'; +import { + spec +} from 'modules/smartadserverBidAdapter'; +import { + newBidder +} from 'src/adapters/bidderFactory'; +import { + config +} from 'src/config'; +import * as utils from 'src/utils'; + +// Default params with optional ones +describe('Smart bid adapter tests', () => { + var DEFAULT_PARAMS = [{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smartadserver', + params: { + domain: 'http://prg.smartadserver.com', + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid', + bidfloor: 0.420, + buId: '7569', + appName: 'Mozilla', + ckId: 42 + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]; + + // Default params without optional ones + var DEFAULT_PARAMS_WO_OPTIONAL = [{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smartadserver', + params: { + domain: 'http://prg.smartadserver.com', + siteId: '1234', + pageId: '5678', + formatId: '90' + }, + requestId: 'efgh5678' + }]; var BID_RESPONSE = { - cpm: 0.42, - ad: 'fake ad content', - width: 300, - height: 250 + body: { + cpm: 12, + width: 300, + height: 250, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake.url', + ad: '< --- awesome script --- >', + cSyncUrl: 'http://awesome.fake.csync.url' + } }; - it('set url parameters', function () { - var stubLoadScript = sinon.stub(adLoader, 'loadScript'); - - adapter().callBids(DEFAULT_PARAMS); - - var smartCallback; - for (var k in $$PREBID_GLOBAL$$) { - if (k.lastIndexOf('sas_', 0) === 0) { - smartCallback = k; - break; + it('Verify build request', () => { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' } - } - - var bidUrl = stubLoadScript.getCall(0).args[0]; - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - expect(parsedBidUrl.hostname).to.equal('www.smartadserver.com'); - expect(parsedBidUrl.pathname).to.equal('/prebid'); - - expect(parsedBidUrlQueryString).to.have.property('pbjscbk').and.to.equal('$$PREBID_GLOBAL$$.' + smartCallback); - expect(parsedBidUrlQueryString).to.have.property('siteid').and.to.equal('1234'); - expect(parsedBidUrlQueryString).to.have.property('pgid').and.to.equal('5678'); - expect(parsedBidUrlQueryString).to.have.property('fmtid').and.to.equal('90'); - expect(parsedBidUrlQueryString).to.have.property('tgt').and.to.equal('test=prebid'); - expect(parsedBidUrlQueryString).to.have.property('ccy').and.to.equal('EUR'); - expect(parsedBidUrlQueryString).to.have.property('bidfloor').and.to.equal('0.42'); - expect(parsedBidUrlQueryString).to.have.property('tag').and.to.equal('sas_42'); - expect(parsedBidUrlQueryString).to.have.property('sizes').and.to.equal('300x250,300x200'); - expect(parsedBidUrlQueryString).to.have.property('async').and.to.equal('1'); - - stubLoadScript.restore(); + }); + const request = spec.buildRequests(DEFAULT_PARAMS); + expect(request[0]).to.have.property('url').and.to.equal('http://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('siteid').and.to.equal('1234'); + expect(requestContent).to.have.property('pageid').and.to.equal('5678'); + expect(requestContent).to.have.property('formatid').and.to.equal('90'); + expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestContent).to.have.property('bidfloor').and.to.equal(0.42); + expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid'); + expect(requestContent).to.have.property('tagId').and.to.equal('sas_42'); + 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(200); + expect(requestContent).to.have.property('pageDomain').and.to.equal(utils.getTopWindowUrl()); + expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + expect(requestContent).to.have.property('buid').and.to.equal('7569'); + expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); + expect(requestContent).to.have.property('ckid').and.to.equal(42); }); - it('test optional parameters default value', function () { - var stubLoadScript = sinon.stub(adLoader, 'loadScript'); - - adapter().callBids(DEFAULT_PARAMS_WO_OPTIONAL); - - var bidUrl = stubLoadScript.getCall(0).args[0]; - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - expect(parsedBidUrlQueryString).to.have.property('tgt').and.to.equal(''); - expect(parsedBidUrlQueryString).to.have.property('ccy').and.to.equal('USD'); - - stubLoadScript.restore(); + it('Verify parse response', () => { + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse(BID_RESPONSE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(12); + expect(bid.adUrl).to.equal('http://awesome.fake.url'); + expect(bid.ad).to.equal('< --- awesome script --- >'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('zioeufg'); + expect(bid.currency).to.equal('GBP'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(DEFAULT_PARAMS[0].bidId); + expect(bid.referrer).to.equal(utils.getTopWindowUrl()); + + expect(function() { spec.interpretResponse(BID_RESPONSE, {data: 'invalid Json'}) }).to.not.throw(); }); - it('creates an empty bid response if no bids', function() { - var stubLoadScript = sinon.stub(adLoader, 'loadScript', function(url) { - var bidUrl = stubLoadScript.getCall(0).args[0]; - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - $$PREBID_GLOBAL$$[parsedBidUrlQueryString.pbjscbk.split('.')[1]](null); - }); - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - adapter().callBids(DEFAULT_PARAMS); - - var bidResponsePlacementCode = stubAddBidResponse.getCall(0).args[0]; - var bidResponseAd = stubAddBidResponse.getCall(0).args[1]; - - expect(bidResponsePlacementCode).to.equal(DEFAULT_PARAMS.bids[0].placementCode); - expect(bidResponseAd.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - expect(bidResponseAd).to.have.property('bidderCode').and.to.equal('smartadserver'); - - stubLoadScript.restore(); - stubAddBidResponse.restore(); + it('Verifies bidder code', () => { + expect(spec.code).to.equal('smartadserver'); }); - it('creates a bid response if bid is returned', function() { - var stubLoadScript = sinon.stub(adLoader, 'loadScript', function(url) { - var bidUrl = stubLoadScript.getCall(0).args[0]; - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - $$PREBID_GLOBAL$$[parsedBidUrlQueryString.pbjscbk.split('.')[1]](BID_RESPONSE); - }); - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + it('Verifies bidder aliases', () => { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('smart'); + }); - adapter().callBids(DEFAULT_PARAMS); + it('Verifies if bid request valid', () => { + 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: { + pageId: 123 + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + siteId: 123 + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + formatId: 123, + pageId: 234 + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + domain: 'www.test.com', + pageId: 234 + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + domain: 'www.test.com', + formatId: 123, + siteId: 456, + pageId: 234 + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + domain: 'www.test.com', + formatId: 123, + siteId: 456, + pageId: 234, + buId: 789, + appName: 'Mozilla' + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + domain: 'www.test.com', + formatId: 123, + pageId: 234, + buId: 789, + appName: 'Mozilla' + } + })).to.equal(false); + }); - var bidResponsePlacementCode = stubAddBidResponse.getCall(0).args[0]; - var bidResponseAd = stubAddBidResponse.getCall(0).args[1]; + it('Verifies user sync', () => { + var syncs = spec.getUserSyncs({iframeEnabled: true}, [BID_RESPONSE]); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('http://awesome.fake.csync.url'); - expect(bidResponsePlacementCode).to.equal(DEFAULT_PARAMS.bids[0].placementCode); - expect(bidResponseAd.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bidResponseAd).to.have.property('bidderCode').and.to.equal('smartadserver'); - expect(bidResponseAd).to.have.property('cpm').and.to.equal(BID_RESPONSE.cpm); - expect(bidResponseAd).to.have.property('ad').and.to.equal(BID_RESPONSE.ad); - expect(bidResponseAd).to.have.property('width').and.to.equal(BID_RESPONSE.width); - expect(bidResponseAd).to.have.property('height').and.to.equal(BID_RESPONSE.height); + syncs = spec.getUserSyncs({iframeEnabled: false}, [BID_RESPONSE]); + expect(syncs).to.have.lengthOf(0); - stubLoadScript.restore(); - stubAddBidResponse.restore(); + syncs = spec.getUserSyncs({iframeEnabled: true}, []); + expect(syncs).to.have.lengthOf(0); }); }); diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index d7c723604a6..858e8bf37a0 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -1,128 +1,230 @@ -import { expect } from 'chai'; -import Adapter from '../../../modules/smartyadsBidAdapter'; -import adapterManager from 'src/adaptermanager'; -import bidManager from 'src/bidmanager'; -import CONSTANTS from 'src/constants.json'; - -describe('Smartyads adapter tests', function () { - let sandbox; - const adUnit = { // TODO CHANGE - code: 'smartyads', - sizes: [[300, 250], [300, 600], [320, 80]], - bids: [{ - bidder: 'smartyads', - params: { - banner_id: 0 - } - }] - }; - - const response = { - ad_id: 0, - adm: '<span>Test Response</span>', - cpm: 0.5, - deal: 'bf063e2e025c', - height: 240, - width: 360 +import {expect} from 'chai'; +import {spec} from '../../../modules/smartyadsBidAdapter'; + +describe('SmartyadsAdapter', () => { + let bid = { + bidId: '23fhj33i987f', + bidder: 'smartyads', + params: { + placementId: 0, + traffic: 'banner' + } }; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); + describe('isBidRequestValid', () => { + it('Should return true if there are bidId, params and placementId parameters present', () => { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', () => { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); }); - describe('Smartyads callBids validation', () => { - let bids, - server; - - beforeEach(() => { - bids = []; - server = sinon.fakeServer.create(); - - sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { - bids.push(bid); - }); + describe('buildRequests', () => { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', () => { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; }); - - afterEach(() => { - server.restore(); + it('Returns POST method', () => { + expect(serverRequest.method).to.equal('POST'); }); - - let adapter = adapterManager.bidderRegistry['smartyads']; - - it('Valid bid-request', () => { - sandbox.stub(adapter, 'callBids'); - adapterManager.callBids({ - adUnits: [clone(adUnit)] - }); - - let bidderRequest = adapter.callBids.getCall(0).args[0]; - - expect(bidderRequest).to.have.property('bids') - .that.is.an('array') - .with.lengthOf(1); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .to.have.property('bidder', 'smartyads'); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .with.property('sizes') - .that.is.an('array') - .with.lengthOf(3) - .that.deep.equals(adUnit.sizes); - expect(bidderRequest).to.have.deep.property('bids[0]') - .with.property('params') - .to.have.property('banner_id', 0); + it('Returns valid URL', () => { + expect(serverRequest.url).to.equal('//ssp-nj.webtradehub.com/?c=o&m=multi'); }); - - it('Valid bid-response', () => { - server.respondWith(JSON.stringify( - response - )); - adapterManager.callBids({ - adUnits: [clone(adUnit)] - }); - server.respond(); - - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bids[0].bidderCode).to.equal('smartyads'); - expect(bids[0].width).to.equal(360); - expect(bids[0].height).to.equal(240); - expect(bids[0].cpm).to.equal(0.5); - expect(bids[0].dealId).to.equal('bf063e2e025c'); + it('Returns valid data if array of bids is valid', () => { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic'); + expect(placement.placementId).to.equal(0); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal('banner'); + }); + it('Returns empty data if no valid requests are passed', () => { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; }); }); - - describe('MAS mapping / ordering', () => { - let masSizeOrdering = Adapter.masSizeOrdering; - - it('should not include values without a proper mapping', () => { - let ordering = masSizeOrdering([[320, 50], [42, 42], [300, 250], [640, 480], [1, 1], [336, 280]]); - expect(ordering).to.deep.equal([15, 16, 43, 65]); + describe('interpretResponse', () => { + it('Should interpret banner response', () => { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); }); - - it('should sort values without any MAS priority sizes in regular ascending order', () => { - let ordering = masSizeOrdering([[320, 50], [640, 480], [336, 280], [200, 600]]); - expect(ordering).to.deep.equal([16, 43, 65, 126]); + it('Should interpret video response', () => { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId'); + expect(dataItem.mediaType).to.not.exist; + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', () => { + const native = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'clickUrl', 'impressionTrackers', 'title', 'image', 'ttl', 'creativeId', 'netRevenue', 'currency'); + expect(dataItem.mediaType).to.not.exist; + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.clickUrl).to.equal('test.com'); + expect(dataItem.title).to.equal('Test'); + expect(dataItem.image).to.equal('test.com'); + expect(dataItem.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', () => { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', () => { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', () => { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', () => { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + describe('getUserSyncs', () => { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', () => { + 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('//ssp-nj.webtradehub.com/?c=o&m=cookie'); }); - - it('should sort MAS priority sizes in the proper order w/ rest ascending', () => { - let ordering = masSizeOrdering([[320, 50], [160, 600], [640, 480], [300, 250], [336, 280], [200, 600]]); - expect(ordering).to.deep.equal([15, 9, 16, 43, 65, 126]); - - ordering = masSizeOrdering([[320, 50], [300, 250], [160, 600], [640, 480], [336, 280], [200, 600], [728, 90]]); - expect(ordering).to.deep.equal([15, 2, 9, 16, 43, 65, 126]); - - ordering = masSizeOrdering([[120, 600], [320, 50], [160, 600], [640, 480], [336, 280], [200, 600], [728, 90]]); - expect(ordering).to.deep.equal([2, 9, 8, 16, 43, 65, 126]); - }) }); }); - -function clone(obj) { - return JSON.parse(JSON.stringify(obj)); -} diff --git a/test/spec/modules/somoaudienceBidAdapter_spec.js b/test/spec/modules/somoaudienceBidAdapter_spec.js new file mode 100644 index 00000000000..2189cacb0ec --- /dev/null +++ b/test/spec/modules/somoaudienceBidAdapter_spec.js @@ -0,0 +1,97 @@ +import {expect} from 'chai'; +import {spec} from 'modules/somoaudienceBidAdapter'; +import {getTopWindowLocation, getTopWindowReferrer} from 'src/utils'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('Somo Audience Adapter Tests', () => { + const bidderSet = [ + { + bidder: 'somoaudience', + params: { + placementId: 'test' + } + }, + ]; + + const bidderAppSet = [ + { + bidder: 'somoaudience', + params: { + placementId: 'test', + app: { + bundle: 'com.somoaudience.apps', + storeUrl: 'http://somoaudience.com/apps', + domain: 'somoaudience.com', + } + } + }, + ]; + + const bidderBadSet = [ + { + bidder: 'somoaudience', + params: { + placement_id: 'test' + } + }, + ]; + + it('Verifies bidder code', () => { + expect(spec.code).to.equal('somoaudience'); + }); + + it('Verifies bidder aliases', () => { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('somo'); + }); + + it('Verifies if bid request valid', () => { + expect(spec.isBidRequestValid(bidderSet[0])).to.equal(true); + expect(spec.isBidRequestValid(bidderAppSet[0])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid(bidderBadSet[0])).to.equal(false); + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect(spec.isBidRequestValid({ params: { placementId: '12345' } })).to.equal(true); + }); + + it('Verifies buildRequests', () => { + const request = spec.buildRequests(bidderSet); + let br = request[0]; + expect(br.url).to.equal('//publisher-east.mobileadtrading.com/rtb/bid?s=test'); + expect(br.method).to.equal('POST'); + const ortbRequest = br.data; + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site.ref).to.equal(getTopWindowReferrer()); + expect(ortbRequest.site.page).to.equal(getTopWindowLocation().href); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.device).to.not.equal(null); + expect(ortbRequest.device.ua).to.equal(navigator.userAgent); + }); + + it('Verify parse response', () => { + const request = spec.buildRequests(bidderSet); + const ortbRequest = request[0].data; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'Somo Test Ad' + }] + }] + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.ad).to.equal('Somo Test Ad'); + }); + it('Verify app requests', () => { + const request = spec.buildRequests(bidderAppSet); + const ortbRequest = request[0].data; + expect(ortbRequest.site).to.equal(null); + expect(ortbRequest.app).to.not.be.null; + expect(ortbRequest.app.bundle).to.equal('com.somoaudience.apps'); + expect(ortbRequest.app.storeurl).to.equal('http://somoaudience.com/apps'); + expect(ortbRequest.app.domain).to.equal('somoaudience.com'); + }); +}); diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 346fc18e637..431bf134349 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -1,389 +1,312 @@ -const chai = require('chai'); -const expect = require('chai').expect; -const Adapter = require('modules/sonobiBidAdapter'); -const bidManager = require('src/bidmanager'); -const adLoader = require('src/adloader'); -const utils = require('src/utils'); +import { expect } from 'chai' +import { spec, _getPlatform } from 'modules/sonobiBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' -chai.config.includeStack = true; +describe('SonobiBidAdapter', () => { + const adapter = newBidder(spec) -describe('Sonobi adapter tests', () => { - // Declared each explicitely so we can loop through and observe each test - const adUnit_p = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_p', - sizes: [[300, 250], [300, 600]], - params: { - placement_id: '1a2b3c4d5e6f1a2b3c4d' - } - }] - }; - const adUnit_pd = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_pd', - sizes: [[300, 250], [300, 600]], - params: { - placement_id: '1a2b3c4d5e6f1a2b3c4d', - dom_id: 'div-gpt-ad-12345-0' - } - }] - }; - const adUnit_pdf = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_pdf', - sizes: [[300, 250], [300, 600]], - params: { - placement_id: '1a2b3c4d5e6f1a2b3c4d', - dom_id: 'div-gpt-ad-12345-0', - floor: '1' - } - }] - }; - const adUnit_a = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_a', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '/7780971/sparks_prebid_MR', - } - }] - }; + describe('.code', () => { + it('should return a bidder code of sonobi', () => { + expect(spec.code).to.equal('sonobi') + }) + }) - const adUnit_as = { - code: 'sbi_s', - sizes: [[120, 600], [300, 600], [160, 600]], - bids: [{ - bidder: 'sonobi', - params: { - ad_unit: '/7780971/sparks_prebid_LB', - sizes: [[300, 250], [300, 600]] - } - }] - }; + describe('inherited functions', () => { + it('should exist and be a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) - const adUnit_ad = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_ad', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '/7780971/sparks_prebid_MR', - dom_id: 'div-gpt-ad-12345-0' - } - }] - }; - const adUnit_af = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_af', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '/7780971/sparks_prebid_MR', - floor: '1' - } - }] - }; - const adUnit_adf = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_adf', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '/7780971/sparks_prebid_MR', - dom_id: 'div-gpt-ad-12345-0', - floor: '1' - } - }] - }; - const adUnit_A = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_A', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '/7780971/sparks_prebid_MR', - } - }] - }; - const adUnit_Ad = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_Ad', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '7780971/sparks_prebid_MR', - dom_id: 'div-gpt-ad-12345-0' - } - }] - }; - const adUnit_Af = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_Af', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '7780971/sparks_prebid_MR', - floor: '1' - } - }] - }; - const adUnit_Adf = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_Adf', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '7780971/sparks_prebid_MR', - dom_id: 'div-gpt-ad-12345-0', - floor: '1' - } - }] - }; - // You guys surprise me all the time new and exciting ways to break this simple adapter. - const adUnit_m1hb = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_m1hb', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '1a2b3c4d5e6f1a2b3c4d', - dom_id: 'div-gpt-ad-12345-0' - } - }] - }; - const adUnit_m2hb = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_m2hb', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '/7780971/sparks_prebid_MR', - placement_id: 'OPTIONAL', - dom_id: 'div-gpt-ad-12345-0', - } - }] - }; - const adUnit_m3hb = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_m3hb', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '/7780971/sparks_prebid_MR', - placement_id: '', - dom_id: 'div-gpt-ad-12345-0', - } - }] - }; - const adUnit_m4hb = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_m4hb', - sizes: [[300, 250], [300, 600]], - params: { - ad_unit: '', - placement_id: '1a2b3c4d5e6f1a2b3c4d', - dom_id: 'div-gpt-ad-12345-0' + describe('.isBidRequestValid', () => { + 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 true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true when bid.params.placement_id and bid.params.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + delete bid.sizes + bid.params = { + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', + 'sizes': [[300, 250], [300, 600]], } - }] - }; - const adUnit_m5hb = { - bidderCode: 'sonobi', - bids: [{ - bidId: 'testbid', - bidder: 'sonobi', - placementCode: 'adUnit_m5hb', - sizes: [[300, 250], [300, 600]], - params: { - placement_id: '/7780971/sparks_prebid_MR', - dom_id: 'div-gpt-ad-12345-0' + + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true when bid.params.placement_id and bid.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + bid.sizes = [[300, 250], [300, 600]] + bid.params = { + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', } - }] - }; - // FTFY - const sbi_adUnits = { - 'adUnit_p': adUnit_p, - 'adUnit_pd': adUnit_pd, - 'adUnit_pdf': adUnit_pdf, - 'adUnit_a': adUnit_a, - 'adUnit_as': adUnit_as, - 'adUnit_ad': adUnit_ad, - 'adUnit_af': adUnit_af, - 'adUnit_adf': adUnit_adf, - 'adUnit_A': adUnit_A, - 'adUnit_Ad': adUnit_Ad, - 'adUnit_Af': adUnit_Af, - 'adUnit_Adf': adUnit_Adf, - 'adUnit_m1hb': adUnit_m1hb, - 'adUnit_m2hb': adUnit_m2hb, - 'adUnit_m3hb': adUnit_m3hb, - 'adUnit_m4hb': adUnit_m4hb, - 'adUnit_m5hb': adUnit_m5hb - }; - // Run the same test against all the (now tons of) different configurations - utils._each(sbi_adUnits, (adUnit, adUnitName) => { - describe('should form valid bid requests', () => { - let adapter = new Adapter(); - let stubLoadScript; - let stubFailBid; - let stubGoodBid; + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) - beforeEach(() => { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - stubFailBid = sinon.stub(adapter, 'failure'); - stubGoodBid = sinon.stub(adapter, 'success'); - }); + it('should return true when bid.params.ad_unit and bid.params.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + delete bid.sizes + bid.params = { + 'ad_unit': '/7780971/sparks_prebid_MR', + 'sizes': [[300, 250], [300, 600]], + } - afterEach(() => { - stubLoadScript.restore(); - stubFailBid.restore(); - stubGoodBid.restore(); - }); + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) - it('should make trinity key:vals for: ' + adUnitName, () => { - let keymakerBid = adapter.formRequest(adUnit.bids); - // Key matches one of two patterns and chai doesn't have an 'or' clause. - expect(Object.keys(keymakerBid)[0]).to.exist; - expect(Object.keys(keymakerBid)[0]).to.not.be.empty; - expect(keymakerBid[Object.keys(keymakerBid)[0]]).to.exist; - expect(keymakerBid[Object.keys(keymakerBid)[0]]).to.not.be.empty; - // Just having a key and val is sufficient for bidder to attempt to work with it. - }); + it('should return true when bid.params.ad_unit and bid.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + bid.sizes = [[300, 250], [300, 600]] + bid.params = { + 'ad_unit': '/7780971/sparks_prebid_MR', + } - it('should attempt to call bidder for: ' + adUnitName, () => { - adapter.callBids(adUnit); - expect(stubLoadScript.callCount).to.equal(1); - expect(stubFailBid.callCount).to.equal(0); - }); - }); - }); + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) - describe('should parse bid returns and register bid objects', () => { - let adapter = new Adapter(); - let spyAddBidResponse; - let stubFailBid; - let stubGoodBid; + it('should return false when no params are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) - const sbi_bid = { - 'slots': - { - 'sbi_a': - { - 'sbi_size': '300x250', - 'sbi_apoc': 'premium', - 'sbi_aid': '159.60.7533347', - 'sbi_mouse': 4.20 - } + it('should return false when bid.params.placement_id and bid.params.ad_unit are not found', () => { + 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) + }) + }) + + describe('.buildRequests', () => { + let bidRequest = [{ + 'bidder': 'sonobi', + 'params': { + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', + 'sizes': [[300, 250], [300, 600]], + 'floor': '1.25', + 'referrer': 'overrides_top_window_location' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1f', + }, + { + 'bidder': 'sonobi', + 'params': { + 'ad_unit': '/7780971/sparks_prebid_LB', + 'sizes': [[300, 250], [300, 600]], + 'referrer': 'overrides_top_window_location' }, - 'sbi_dc': 'mco-1-' + 'adUnitCode': 'adunit-code-2', + 'sizes': [[120, 600], [300, 600], [160, 600]], + 'bidId': '30b31c1838de1e', + }]; + + let keyMakerData = { + '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25', + '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600', }; - const sbi_video_bid = { - 'slots': - { - 'sbi_a': + it('should return a properly formatted request', () => { + const bidRequests = spec.buildRequests(bidRequest) + const bidRequestsPageViewID = spec.buildRequests(bidRequest) + 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)) + expect(bidRequests.data.ref).not.to.be.empty + expect(bidRequests.data.s).not.to.be.empty + expect(bidRequests.data.pv).to.equal(bidRequestsPageViewID.data.pv) + expect(bidRequests.data.hfa).to.not.exist + expect(bidRequests.bidderRequests).to.eql(bidRequest); + expect(bidRequests.data.ref).to.equal('overrides_top_window_location'); + expect(['mobile', 'tablet', 'desktop']).to.contain(bidRequests.data.vp); + }) + + it('should return a properly formatted request with hfa', () => { + bidRequest[0].params.hfa = 'hfakey' + bidRequest[1].params.hfa = 'hfakey' + const bidRequests = spec.buildRequests(bidRequest) + 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 return null if there is nothing to bid on', () => { + const bidRequests = spec.buildRequests([{params: {}}]) + expect(bidRequests).to.equal(null); + }) + }) + + describe('.interpretResponse', () => { + const bidRequests = { + 'method': 'GET', + 'url': 'https://apex.go.sonobi.com/trinity.json', + 'withCredentials': true, + 'data': { + 'key_maker': '{"30b31c1838de1f":"1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25","/7780971/sparks_prebid_LB|30b31c1838de1e":"300x250,300x600"}', 'ref': 'localhost:9876', 's': '2474372d-c0ff-4f46-aef4-a173058403d9', 'pv': 'c9cfc207-cd83-4a01-b591-8bb29389d4b0' + }, + 'bidderRequests': [ + { + 'bidder': 'sonobi', + 'params': { + 'ad_unit': '/7780971/sparks_prebid_LB', + 'sizes': [[300, 250], [300, 600]], + 'floor': '1.25' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1f' + }, + { + 'bidder': 'sonobi', + 'params': { + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', + 'sizes': [[300, 250], [300, 600]] + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[120, 600], [300, 600], [160, 600]], + 'bidId': '30b31c1838de1e', + 'mediaType': 'video' + }, { - 'sbi_size': 'outstream', - 'sbi_apoc': 'premium', - 'sbi_aid': '159.60.7533347', - 'sbi_mouse': 4.20, + 'bidder': 'sonobi', + 'params': { + 'ad_unit': '/7780971/sparks_prebid_LB', + 'sizes': [[300, 250], [300, 600]] + }, + 'adUnitCode': 'adunit-code-3', + 'sizes': [[120, 600], [300, 600], [160, 600]], + 'bidId': '30b31c1838de1g' } - }, - 'sbi_dc': 'mco-1-' + ] }; - const sbi_deal_bid = { - 'slots': - { - 'sbi_a': - { - 'sbi_size': '300x250', - 'sbi_apoc': 'premium', - 'sbi_aid': '159.60.7533347', - 'sbi_mouse': 4.20, - 'sbi_dozer': 'apex-test-deal' - } - }, - 'sbi_dc': 'mco-1-' + let bidResponse = { + 'body': { + 'slots': { + '/7780971/sparks_prebid_LB|30b31c1838de1f': { + 'sbi_size': '300x600', + 'sbi_apoc': 'remnant', + 'sbi_aid': '30292e432662bd5f86d90774b944b039', + 'sbi_mouse': 1.07, + }, + '30b31c1838de1e': { + 'sbi_size': '300x250', + 'sbi_apoc': 'remnant', + 'sbi_aid': '30292e432662bd5f86d90774b944b038', + 'sbi_mouse': 1.25, + 'sbi_dozer': 'dozerkey', + }, + '/7780971/sparks_prebid_LB|30b31c1838de1g': {}, + }, + 'sbi_dc': 'mco-1-', + 'sbi_px': [{ + 'code': 'so', + 'delay': 0, + 'url': 'https://example.com/pixel.png', + 'type': 'image' + }], + 'sbi_suid': 'af99f47a-e7b1-4791-ab32-34952d87c5a0', + } }; - const sbi_noBid = { - 'slots': + let prebidResponse = [ { - 'sbi_a': {} + 'requestId': '30b31c1838de1f', + 'cpm': 1.07, + 'width': 300, + 'height': 600, + 'ad': '<script type="text/javascript" src="https://mco-1-apex.go.sonobi.com/sbi.js?aid=30292e432662bd5f86d90774b944b039&as=null&ref=localhost:9876"></script>', + 'ttl': 500, + 'creativeId': '30292e432662bd5f86d90774b944b039', + 'netRevenue': true, + 'currency': 'USD' }, - 'sbi_dc': 'mco-1-' - }; - - beforeEach(() => { - spyAddBidResponse = sinon.spy(bidManager, 'addBidResponse'); - stubFailBid = sinon.stub(adapter, 'failure'); - stubGoodBid = sinon.stub(adapter, 'success'); - }); + { + 'requestId': '30b31c1838de1e', + 'cpm': 1.25, + 'width': 300, + 'height': 250, + 'ad': 'https://mco-1-apex.go.sonobi.com/vast.xml?vid=30292e432662bd5f86d90774b944b038&ref=localhost:9876', + 'ttl': 500, + 'creativeId': '30292e432662bd5f86d90774b944b038', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'dozerkey' + } + ]; - afterEach(() => { - spyAddBidResponse.restore(); - stubFailBid.restore(); - stubGoodBid.restore(); - }); + it('should map bidResponse to prebidResponse', () => { + const response = spec.interpretResponse(bidResponse, bidRequests); + expect(response).to.deep.equal(prebidResponse); + }) + }) - it('should create bid object for good bid return', () => { - adapter.parseResponse(sbi_bid); - expect(spyAddBidResponse.called).to.be.true; - expect(stubFailBid.callCount).to.equal(0); - }); + describe('.getUserSyncs', () => { + let bidResponse = [{ + 'body': { + 'sbi_px': [{ + 'code': 'so', + 'delay': 0, + 'url': 'https://pixel-test', + 'type': 'image' + }] + } + }]; - it('should create bid object for outstream video bid return', () => { - adapter.parseResponse(sbi_video_bid); - expect(spyAddBidResponse.called).to.be.true; - expect(stubFailBid.callCount).to.equal(0); - }); + it('should return one sync pixel', () => { + expect(spec.getUserSyncs({ pixelEnabled: true }, bidResponse)).to.deep.equal([{ + type: 'image', + url: 'https://pixel-test' + }]); + }) + it('should return an empty array when sync is enabled but there are no bidResponses', () => { + expect(spec.getUserSyncs({ pixelEnabled: true }, [])).to.have.length(0); + }) - it('should create bid object for deal bid return', () => { - adapter.parseResponse(sbi_deal_bid); - expect(spyAddBidResponse.called).to.be.true; - expect(stubFailBid.callCount).to.equal(0); - }); + it('should return an empty array when sync is enabled but no sync pixel returned', () => { + const pixel = Object.assign({}, bidResponse); + delete pixel[0].body.sbi_px; + expect(spec.getUserSyncs({ pixelEnabled: true }, bidResponse)).to.have.length(0); + }) - it('should create fail bid object for empty return', () => { - adapter.parseResponse(sbi_noBid); - expect(spyAddBidResponse.called).to.be.true; - expect(stubGoodBid.callCount).to.equal(0); - }); - }); -}); + it('should return an empty array', () => { + expect(spec.getUserSyncs({ pixelEnabled: false }, bidResponse)).to.have.length(0); + }) + }) + describe('_getPlatform', () => { + it('should return mobile', () => { + expect(_getPlatform({innerWidth: 767})).to.equal('mobile') + }) + it('should return tablet', () => { + expect(_getPlatform({innerWidth: 800})).to.equal('tablet') + }) + it('should return desktop', () => { + expect(_getPlatform({innerWidth: 1000})).to.equal('desktop') + }) + }) +}) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index c5ad1b2cb25..b19b79c7886 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -1,178 +1,224 @@ -import {expect} from 'chai'; -import Adapter from 'modules/sovrnBidAdapter'; -import bidmanager from 'src/bidmanager'; -import adloader from 'src/adloader'; -var utils = require('src/utils'); - -describe('sovrn adapter tests', function () { - let adapter; - const bidderRequest = { - bidderCode: 'sovrn', - bids: [ - { - bidId: 'bidId1', - bidder: 'sovrn', - params: { - tagid: '315045', - bidfloor: 1.25 - }, - sizes: [[320, 50]], - placementCode: 'div-gpt-ad-12345-1' - }, - { - bidId: 'bidId2', - bidder: 'sovrn', - params: { - tagid: '315046' - }, - sizes: [[320, 50]], - placementCode: 'div-gpt-ad-12345-2' - }, - { - bidId: 'bidId3', - bidder: 'sovrn', - params: { - tagid: '315047' - }, - sizes: [[320, 50]], - placementCode: 'div-gpt-ad-12345-2' - }, - ] - }; +import { expect } from 'chai'; +import { spec } from 'modules/sovrnBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { REPO_AND_VERSION } from 'src/constants'; - beforeEach(() => adapter = new Adapter()); +const ENDPOINT = `//ap.lijit.com/rtb/bid?src=${REPO_AND_VERSION}`; - describe('requestBids', function () { - let stubLoadScript; +describe('sovrnBidAdapter', function() { + const adapter = newBidder(spec); - beforeEach(() => { - stubLoadScript = sinon.stub(adloader, 'loadScript'); - }); - - afterEach(() => { - stubLoadScript.restore(); + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'sovrn', + 'params': { + 'tagid': '403370' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); + it('should return false when tagid not passed correctly', () => { + bid.params.tagid = 'ABCD'; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('loads the request script', function () { - adapter.callBids(bidderRequest); - - let sovrnScript = decodeURIComponent(stubLoadScript.getCall(0).args[0]); - let firstExpectedImpObj = '{"id":"bidId1","banner":{"w":320,"h":50},"tagid":"315045","bidfloor":1.25}'; - let secondExpectedImpObj = '{"id":"bidId2","banner":{"w":320,"h":50},"tagid":"315046","bidfloor":""}'; - - expect(sovrnScript).to.contain(firstExpectedImpObj); - expect(sovrnScript).to.contain(secondExpectedImpObj); + it('should return false when require params are not passed', () => { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); - describe('sovrnResponse', function () { - let stubAddBidResponse; - let getRequestStub; - let getRequestsStub; - - beforeEach(() => { - stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - getRequestStub = sinon.stub(utils, 'getBidRequest'); - getRequestStub.withArgs(bidderRequest.bids[0].bidId).returns(bidderRequest.bids[0]); - getRequestStub.withArgs(bidderRequest.bids[1].bidId).returns(bidderRequest.bids[1]); - getRequestStub.withArgs(bidderRequest.bids[2].bidId).returns(bidderRequest.bids[2]); - - getRequestsStub = sinon.stub(utils, 'getBidderRequestAllAdUnits'); - getRequestsStub.returns(bidderRequest); + describe('buildRequests', () => { + const bidRequests = [{ + 'bidder': 'sovrn', + 'params': { + 'tagid': '403370' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + + const request = spec.buildRequests(bidRequests); + + it('sends bid request to our endpoint via POST', () => { + expect(request.method).to.equal('POST'); }); - afterEach(() => { - stubAddBidResponse.restore(); - getRequestStub.restore(); - getRequestsStub.restore(); + it('attaches source and version to endpoint URL as query params', () => { + expect(request.url).to.equal(ENDPOINT) }); - it('should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.sovrnResponse).to.exist.and.to.be.a('function'); + it('sends \'iv\' as query param if present', () => { + const ivBidRequests = [{ + 'bidder': 'sovrn', + 'params': { + 'tagid': '403370', + 'iv': 'vet' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + const request = spec.buildRequests(ivBidRequests); + + expect(request.data).to.contain('"iv":"vet"') }); - it('should add empty bid responses if no bids returned', function () { - let response = { - 'id': '54321', - 'seatbid': [] + it('sends gdpr info if exists', () => { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'sovrn', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true + } }; + bidderRequest.bids = bidRequests; - $$PREBID_GLOBAL$$.sovrnResponse(response); + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); - let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - let bidObject1 = stubAddBidResponse.getCall(0).args[1]; - let bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0]; - let bidObject2 = stubAddBidResponse.getCall(1).args[1]; - let bidPlacementCode3 = stubAddBidResponse.getCall(2).args[0]; - let bidObject3 = stubAddBidResponse.getCall(2).args[1]; + expect(payload.regs.ext.gdpr).to.exist.and.to.be.a('number'); + expect(payload.regs.ext.gdpr).to.equal(1); + expect(payload.user.ext.consent).to.exist.and.to.be.a('string'); + expect(payload.user.ext.consent).to.equal(consentString); + }); + + it('converts tagid to string', () => { + const ivBidRequests = [{ + 'bidder': 'sovrn', + 'params': { + 'tagid': 403370, + 'iv': 'vet' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + const request = spec.buildRequests(ivBidRequests); + + expect(request.data).to.contain('"tagid":"403370"') + }) + }); - expect(bidPlacementCode1).to.equal('div-gpt-ad-12345-1'); - expect(bidObject1.getStatusCode()).to.equal(2); - expect(bidObject1.bidderCode).to.equal('sovrn'); + describe('interpretResponse', () => { + let response; + beforeEach(() => { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', + 'crid': 'creativelycreatedcreativecreative', + 'impid': '263c448586f5a1', + 'price': 0.45882675, + 'nurl': '<!-- NURL -->', + 'adm': '<!-- Creative -->', + 'h': 90, + 'w': 728 + }] + }] + } + }; + }); - expect(bidPlacementCode2).to.equal('div-gpt-ad-12345-2'); - expect(bidObject2.getStatusCode()).to.equal(2); - expect(bidObject2.bidderCode).to.equal('sovrn'); + it('should get the correct bid response', () => { + let expectedResponse = [{ + 'requestId': '263c448586f5a1', + 'cpm': 0.45882675, + 'width': 728, + 'height': 90, + 'creativeId': 'creativelycreatedcreativecreative', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': decodeURIComponent(`<!-- Creative --><img src=<!-- NURL -->>`), + 'ttl': 60000 + }]; + + let result = spec.interpretResponse(response); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); - expect(bidPlacementCode3).to.equal('div-gpt-ad-12345-2'); - expect(bidObject3.getStatusCode()).to.equal(2); - expect(bidObject3.bidderCode).to.equal('sovrn'); + it('crid should default to the bid id if not on the response', () => { + delete response.body.seatbid[0].bid[0].crid; + let expectedResponse = [{ + 'requestId': '263c448586f5a1', + 'cpm': 0.45882675, + 'width': 728, + 'height': 90, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': decodeURIComponent(`<!-- Creative --><img src=<!-- NURL -->>`), + 'ttl': 60000 + }]; + + let result = spec.interpretResponse(response); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); - stubAddBidResponse.calledThrice; + it('should get correct bid response when dealId is passed', () => { + response.body.dealid = 'baking'; + + let expectedResponse = [{ + 'requestId': '263c448586f5a1', + 'cpm': 0.45882675, + 'width': 728, + 'height': 90, + 'creativeId': 'creativelycreatedcreativecreative', + 'dealId': 'baking', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': decodeURIComponent(`<!-- Creative --><img src=<!-- NURL -->>`), + 'ttl': 60000 + }]; + + let result = spec.interpretResponse(response); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); }); - it('should add a bid response for bids returned and empty bid responses for the rest', function () { + it('handles empty bid response', () => { let response = { - 'id': '54321111', - 'seatbid': [ { - 'bid': [ { - 'id': '1111111', - 'impid': 'bidId2', - 'price': 0.09, - 'nurl': 'http://url', - 'adm': 'ad-code', - 'h': 250, - 'w': 300, - 'dealid': 'ADEAL123', - 'ext': { } - } ] - } ] + body: { + 'id': '37386aade21a71', + 'seatbid': [] + } }; - - $$PREBID_GLOBAL$$.sovrnResponse(response); - - let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - let bidObject1 = stubAddBidResponse.getCall(0).args[1]; - let bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0]; - let bidObject2 = stubAddBidResponse.getCall(1).args[1]; - let bidPlacementCode3 = stubAddBidResponse.getCall(2).args[0]; - let bidObject3 = stubAddBidResponse.getCall(2).args[1]; - - expect(bidPlacementCode1).to.equal('div-gpt-ad-12345-2'); - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.bidderCode).to.equal('sovrn'); - expect(bidObject1.creative_id).to.equal('1111111'); - expect(bidObject1.cpm).to.equal(0.09); - expect(bidObject1.height).to.equal(250); - expect(bidObject1.width).to.equal(300); - expect(bidObject1.ad).to.equal('ad-code<img src="http://url">'); - expect(bidObject1.adId).to.equal('bidId2'); - expect(bidObject1.dealId).to.equal('ADEAL123'); - - expect(bidPlacementCode2).to.equal('div-gpt-ad-12345-1'); - expect(bidObject2.getStatusCode()).to.equal(2); - expect(bidObject2.bidderCode).to.equal('sovrn'); - - expect(bidPlacementCode3).to.equal('div-gpt-ad-12345-2'); - expect(bidObject3.getStatusCode()).to.equal(2); - expect(bidObject3.bidderCode).to.equal('sovrn'); - - stubAddBidResponse.calledThrice; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); }); }); }); diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js deleted file mode 100644 index 1b48111b79e..00000000000 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ /dev/null @@ -1,226 +0,0 @@ -import {expect} from 'chai'; -import {assert} from 'chai'; -import Adapter from 'modules/spotxBidAdapter'; -import bidManager from 'src/bidmanager'; -import adLoader from 'src/adloader'; - -const CHANNEL_ID = 85394; -const CACHE_KEY = 'eyJob3N0IjoiZmUwMDEuc3BvdHguZ2FkZ2V0cy5sb2QiLCJja'; - -let bidRequest = { - 'bidderCode': 'spotx', - 'requestId': '4b8bb067-fca9-478b-9207-d24b87fce85c', - 'bidderRequestId': '1bfc89fa86fd1d', - 'timeout': 1000, - 'bids': [ - { - 'bidId': '2626663210bd26', - 'bidder': 'spotx', - 'bidderRequestId': '145a190a61161e', - 'mediaType': 'video', - 'params': { - 'placementId': '123456789', - 'video': { - 'ad_mute': false, - 'autoplay': true, - 'channel_id': CHANNEL_ID, - 'hide_skin': false, - 'slot': null, - 'video_slot': null - } - }, - 'placementCode': 'video1', - 'requestId': '5e1e93aa-55cf-4f73-a56a-8a74d0584c5f', - 'sizes': [[640, 480]], - 'transactionId': 'df629792-c9ae-481e-9ce1-eaa83bde4cdb' - } - ] -}; - -let badBidRequest = { - 'bidderCode': 'spotx', - 'bids': [ - { - 'bidId': '2626663210bd26', - 'bidder': 'spotx', - 'mediaType': 'video', - 'params': { - 'placementId': '123456789', - 'video': { - 'slot': 'contentSpotx', - 'video_slot': 'contentElementSpotx' - } - }, - 'placementCode': 'video1', - } - ] -}; - -var xhrResponse = JSON.stringify({ - 'id': CHANNEL_ID.toString(), - 'cur': 'USD', - 'seatbid': [ - { - 'bid': [ - { - 'id': '47e.fc9b5.90ede6', - 'impid': '1497549328279', - 'impression_guid': 'e2514a4651f311e7b50f113c04e90000', - 'price': '20', - 'adm': '<VAST><Ad><Wrapper><VASTAdTagURI><![CDATA[http:\/\/search.spotxchange.com\/\/ad\/vast.html?key=' + - CACHE_KEY + ']]><\/VASTAdTagURI><\/Wrapper><\/Ad><\/VAST>', - 'adomain': 'null', - 'crid': '47e.fc9b5.90ede6', - 'cid': 'null', - 'ext': { - 'cache_key': CACHE_KEY - } - } - ] - } - ] -}); - -describe('spotx adapter tests', () => { - describe('callBids', () => { - let server; - let adapter; - - beforeEach(() => { - adapter = new Adapter(); - - var slot = document.createElement('div'); - slot.setAttribute('id', 'contentSpotx'); - document.body.appendChild(slot); - - var videoSlot = document.createElement('video'); - videoSlot.setAttribute('id', 'contentElementSpotx'); - slot.appendChild(videoSlot); - - var source1 = document.createElement('source'); - source1.setAttribute('src', 'http://rmcdn.2mdn.net/Demo/vast_inspector/android.mp4'); - videoSlot.appendChild(source1); - - bidRequest.bids[0].params.video.slot = slot; - bidRequest.bids[0].params.video.video_slot = videoSlot; - - server = sinon.fakeServer.create(); - server.respondImmediately = true; - }); - - afterEach(() => { - var slot = document.getElementById('contentSpotx'); - while (slot.firstChild) { - slot.removeChild(slot.firstChild); - } - var body = slot.parentElement; - body.removeChild(slot); - - server.restore(); - }); - - it('should load Direct AdOS onto page', () => { - sinon.spy(adLoader, 'loadScript'); - - adapter.callBids(bidRequest); - - sinon.assert.calledOnce(adLoader.loadScript); - expect(adLoader.loadScript.firstCall.args[0]).to.equal('//js.spotx.tv/directsdk/v1/' + CHANNEL_ID + '.js'); - expect(adLoader.loadScript.firstCall.args[1]).to.be.a('function'); - - adLoader.loadScript.restore(); - }); - - it('should not load Direct AdOS onto page if no bid is provided', () => { - sinon.spy(adLoader, 'loadScript'); - - adapter.callBids(); - sinon.assert.notCalled(adLoader.loadScript); - adLoader.loadScript.restore(); - }); - - describe('bid response tests', () => { - let loadScriptStub; - let getAdServerKVPsStub; - - before(() => { - let response = { - spotx_bid: 20, - spotx_ad_key: CACHE_KEY - }; - - getAdServerKVPsStub = sinon.stub(); - getAdServerKVPsStub.onCall(0).returns({ - then: function (successCb) { - return successCb(response); - } - }); - - getAdServerKVPsStub.onCall(1).returns({ - then: function (successCb, failureCb) { - return failureCb(); - } - }); - - window.SpotX = { - DirectAdOS: function(options) { - return { - getAdServerKVPs: getAdServerKVPsStub - } - } - }; - - loadScriptStub = sinon.stub(adLoader, 'loadScript', function(url, callback) { - callback(); - }); - }); - - after(() => { - loadScriptStub.restore(); - }); - - it('should add bid response on success', (done) => { - sinon.stub(bidManager, 'addBidResponse', (placementCode, bid) => { - expect(placementCode).to.equal('video1'); - expect(bid.bidderCode).to.equal('spotx'); - expect(bid.cpm).to.equal(20); - expect(bid.mediaType).to.equal('video'); - expect(bid.statusMessage).to.equal('Bid available'); - expect(bid.vastUrl).to.equal('//search.spotxchange.com/ad/vast.html?key=' + CACHE_KEY); - - bidManager.addBidResponse.restore(); - done(); - }); - - server.respondWith((request) => { - if (request.url.match(/openrtb\/2.3\/dados/) && request.method === 'POST') { - request.respond(200, {}, xhrResponse); - } - }); - - adapter.callBids(bidRequest); - }); - - it('should add failed bid response on error', (done) => { - sinon.stub(bidManager, 'addBidResponse', (placementCode, bid) => { - expect(placementCode).to.equal('video1'); - expect(bid.bidderCode).to.equal('spotx'); - expect(bid.statusMessage).to.equal('Bid returned empty or error response'); - expect(bid.cpm).to.be.undefined; - expect(bid.vastUrl).to.be.undefined; - - bidManager.addBidResponse.restore(); - done(); - }); - - server.respondWith((request) => { - if (request.url.match(/openrtb\/2.3\/dados/) && request.method === 'POST') { - request.respond(204, {}, ''); - } - }); - - adapter.callBids(bidRequest); - }); - }); - }); -}); diff --git a/test/spec/modules/stickyadstvBidAdapter_spec.js b/test/spec/modules/stickyadstvBidAdapter_spec.js deleted file mode 100644 index c0f35b1c406..00000000000 --- a/test/spec/modules/stickyadstvBidAdapter_spec.js +++ /dev/null @@ -1,225 +0,0 @@ -import {expect} from 'chai'; -import {assert} from 'chai'; -import Adapter from '../../../modules/stickyadstvBidAdapter'; -import adLoader from '../../../src/adloader'; - -describe('StickyAdsTV Adapter', function () { - var adapter = void 0; - var sandbox = void 0; - var bidsRequestBuff = void 0; - var bidderRequest = { - bidderCode: 'stickyadstv', - bids: [{ - bidId: 'bidId1', - bidder: 'stickyadstv', - placementCode: 'foo', - sizes: [[300, 250]], - params: { - zoneId: '2003', - format: 'screen-roll' - } - }, { - bidId: 'bidId2', - bidder: 'stickyadstv', - placementCode: 'bar', - sizes: [[728, 90]], - params: { - zoneId: '5562003' - } - }, { - bidId: 'bidId3', - bidder: 'stickyadstv', - placementCode: '', - sizes: [[300, 600]], - params: { - zoneId: '123456' - } - }, { - bidId: 'bidId4', - bidder: 'stickyadstv', - placementCode: 'coo', - sizes: [[300, 600]], - params: { - wrong: 'missing zoneId' - } - }] - }; - - beforeEach(function () { - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - bidsRequestBuff = $$PREBID_GLOBAL$$._bidsRequested; - $$PREBID_GLOBAL$$._bidsRequested = []; - }); - - afterEach(function () { - sandbox.restore(); - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestBuff; - }); - - describe('callBids', function () { - beforeEach(function () { - sandbox.stub(adLoader, 'loadScript'); - adapter.callBids(bidderRequest); - }); - - it('should be called twice', function () { - sinon.assert.calledTwice(adLoader.loadScript); - }); - - it('should have load screenroll and mustang script', function () { - var url = void 0; - - url = adLoader.loadScript.firstCall.args[0]; - expect(url).to.equal('//cdn.stickyadstv.com/prime-time/screen-roll.min.js'); - - url = adLoader.loadScript.secondCall.args[0]; - expect(url).to.equal('//cdn.stickyadstv.com/mustang/mustang.min.js'); - }); - }); - - describe('getBid', function () { - let bidResponse; - let loadConfig; - let getPricingCalled; - - beforeEach(function () { - // Mock VastLoader for test purpose - window.com = { - stickyadstv: { - vast: { - VastLoader: function() { - this.getVast = function() { - return { - getPricing: function() { - getPricingCalled = true; - return {currency: 'USD', price: 4.000} - } - }; - }; - - this.load = function(config, listener) { - loadConfig = config; - listener.onSuccess(); - }; - } - }, - screenroll: { - getPlayerSize: function() { - return '123x456'; - } - } - } - }; - - adapter.getBid(bidderRequest.bids[0], function(bidObject) { - bidResponse = bidObject; - }); - }); - - afterEach(function() { - delete window.com.stickyadstv.vast.VastLoader; - delete window.com.stickyadstv.vast; - delete window.com.stickyadstv.screenroll; - delete window.com.stickyadstv; - }); - - it('should return a valid bidObject', function () { - expect(bidResponse).to.have.property('cpm', 4.000); - expect(bidResponse).to.have.property('ad', "<script type=\'text/javascript\'>var topWindow = (function(){var res=window; try{while(top != res){if(res.parent.location.href.length)res=res.parent;}}catch(e){}return res;})();var vast = topWindow.stickyadstv_cache[\"foo\"];var config = { preloadedVast:vast, ASLoader:topWindow.stickyadstv_asLoader,domId:\"foo\"};topWindow.com.stickyadstv.screenroll.start(config);</script>"); - expect(bidResponse).to.have.property('bidderCode', 'stickyadstv'); - expect(bidResponse).to.have.property('currencyCode', 'USD'); - expect(bidResponse).to.have.property('width', 300); - expect(bidResponse).to.have.property('height', 250); - expect(bidResponse.getStatusCode()).to.equal(1); - }); - - it('should have called load with proper config', function () { - expect(loadConfig).to.have.property('playerSize', '123x456'); - expect(loadConfig).to.have.property('zoneId', '2003'); - }); - - it('should have called getPricing', function () { - expect(getPricingCalled).to.equal(true); - }); - }); - - describe('formatBidObject', function () { - it('should create a valid bid object', function () { - let result = adapter.formatBidObject('', true, {currency: 'EUR', price: '1.2345'}, '<div>sample</div>', 200, 300); - - expect(result).to.have.property('cpm', '1.2345'); - expect(result).to.have.property('ad', '<div>sample</div>'); - expect(result).to.have.property('currencyCode', 'EUR'); - expect(result).to.have.property('width', 200); - expect(result).to.have.property('height', 300); - expect(result.getStatusCode()).to.equal(1); - }); - - it('should create a invalid bid object because price is not defined', function () { - let result = adapter.formatBidObject('', true, null, '<div>sample</div>', 200, 300); - - expect(result.getStatusCode()).to.equal(2); - }); - - it('should create a invalid bid object', function () { - let result = adapter.formatBidObject('', false, {currency: 'EUR', price: '1.2345'}, '<div>sample</div>', 200, 300); - - expect(result.getStatusCode()).to.equal(2); - }); - }); - - describe('formatAdHTML', function () { - it('should create an inBanner ad format', function () { - let result = adapter.formatAdHTML({placementCode: 'placementCodeValue', params: {}}, [200, 300]); - - expect(result).to.equal('<div id="stickyadstv_prebid_target"></div><script type=\'text/javascript\'>var topWindow = (function(){var res=window; try{while(top != res){if(res.parent.location.href.length)res=res.parent;}}catch(e){}return res;})();var vast = topWindow.stickyadstv_cache["placementCodeValue"];var config = { preloadedVast:vast, autoPlay:true};var ad = new topWindow.com.stickyadstv.vpaid.Ad(document.getElementById("stickyadstv_prebid_target"),config);if(topWindow.stickyadstv_asLoader) topWindow.stickyadstv_asLoader.registerEvents(ad);ad.initAd(200,300,"",0,"","");</script>'); - }); - - it('should create an intext ad format', function () { - let result = adapter.formatAdHTML({placementCode: 'placementCodeValue', params: {format: 'intext-roll', auto: 'v2', smartPlay: 'true'}}, [200, 300]); - - expect(result).to.equal('<script type=\'text/javascript\'>var topWindow = (function(){var res=window; try{while(top != res){if(res.parent.location.href.length)res=res.parent;}}catch(e){}return res;})();var vast = topWindow.stickyadstv_cache["placementCodeValue"];var config = { preloadedVast:vast, ASLoader:topWindow.stickyadstv_asLoader,auto:"v2",smartPlay:"true"};topWindow.com.stickyadstv.intextroll.start(config);</script>'); - }); - - it('should create a screenroll ad format', function () { - let result = adapter.formatAdHTML({placementCode: 'placementCodeValue', params: {format: 'screen-roll', smartPlay: 'true'}}, [200, 300]); - - expect(result).to.equal('<script type=\'text/javascript\'>var topWindow = (function(){var res=window; try{while(top != res){if(res.parent.location.href.length)res=res.parent;}}catch(e){}return res;})();var vast = topWindow.stickyadstv_cache["placementCodeValue"];var config = { preloadedVast:vast, ASLoader:topWindow.stickyadstv_asLoader,smartPlay:"true",domId:"placementCodeValue"};topWindow.com.stickyadstv.screenroll.start(config);</script>'); - }); - }); - - describe('getBiggerSize', function () { - it('should return the bigger size', function () { - let result = adapter.getBiggerSize([[1, 4000], [4000, 1], [200, 300], [0, 0]]); - - expect(result[0]).to.equal(200); - expect(result[1]).to.equal(300); - }); - }); - - describe('top most window', function () { - it('should return the top most window', function () { - let result = adapter.getTopMostWindow(); - - expect(result).to.equal(window.top); - }); - }); - - describe('get component id', function() { - it('should return valid component ids', function() { - expect(adapter.getComponentId('inbanner')).to.equal('mustang'); - expect(adapter.getComponentId('intext-roll')).to.equal('intext-roll'); - expect(adapter.getComponentId('screen-roll')).to.equal('screen-roll'); - }); - }); - - describe('get API name', function() { - it('should return valid API names', function() { - expect(adapter.getAPIName()).to.equal(''); - expect(adapter.getAPIName('intext-roll')).to.equal('intextroll'); - expect(adapter.getAPIName('screen-roll')).to.equal('screenroll'); - expect(adapter.getAPIName('floorad')).to.equal('floorad'); - }); - }); -}); diff --git a/test/spec/modules/tapsenseBidAdapter_spec.js b/test/spec/modules/tapsenseBidAdapter_spec.js deleted file mode 100644 index 71f61af2a65..00000000000 --- a/test/spec/modules/tapsenseBidAdapter_spec.js +++ /dev/null @@ -1,257 +0,0 @@ -import { expect } from 'chai'; -import Adapter from 'modules/tapsenseBidAdapter'; -import bidmanager from 'src/bidmanager'; -import adloader from 'src/adloader'; -import * as utils from 'src/utils'; - -const DEFAULT_BIDDER_REQUEST = { - 'bidderCode': 'tapsense', - 'bidderRequestId': '141ed07a281ca3', - 'requestId': 'b202e550-b0f7-4fb9-bfb4-1aa80f1795b4', - 'start': new Date().getTime(), - 'bids': [ - { - 'sizes': undefined, // set values in tests - 'bidder': 'tapsense', - 'bidId': '2b211418dd0575', - 'bidderRequestId': '141ed07a281ca3', - 'placementCode': 'thisisatest', - 'params': { - 'ufid': 'thisisaufid', - 'refer': 'thisisarefer', - 'version': '0.0.1', - 'ad_unit_id': 'thisisanadunitid', - 'device_id': 'thisisadeviceid', - 'lat': 'thisislat', - 'long': 'thisisalong', - 'user': 'thisisanidfa', - 'price_floor': 0.01 - } - } - ] -}; - -const SUCCESSFUL_RESPONSE = { - 'count_ad_units': 1, - 'status': { - 'value': 'ok', - }, - 'ad_units': [ - { - html: '<html><head></head><body></body></html>', - imp_url: 'https://i.tapsense.com' - } - ], - 'id': 'thisisanid', - 'width': 320, - 'height': 50, - 'time': new Date().getTime() -} - -const UNSUCCESSFUL_RESPONSE = { - 'count_ad_units': 0, - 'status': { - 'value': 'nofill' // will be set in test - }, - 'time': new Date().getTime() -} - -function duplicate(obj) { - return JSON.parse(JSON.stringify(obj)); -} - -function makeSuccessfulRequest(adapter) { - let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); - modifiedReq.bids[0].sizes = [[320, 50], [500, 500]]; - adapter.callBids(modifiedReq); - return modifiedReq.bids; -} - -describe('TapSenseAdapter', () => { - let adapter, sandbox; - beforeEach(() => { - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - }); - afterEach(() => { - sandbox.restore(); - }) - - describe('request function', () => { - beforeEach(() => { - sandbox.stub(adloader, 'loadScript'); - }); - afterEach(() => { - sandbox.restore(); - }); - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - it('requires parameters to make request', () => { - adapter.callBids({}); - sinon.assert.notCalled(adloader.loadScript); - }); - it('does not make a request if missing user', () => { - let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); - delete modifiedReq.bids.user - adapter.callBids(modifiedReq); - sinon.assert.notCalled(adloader.loadScript); - }); - it('does not make a request if missing ad_unit_id', () => { - let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); - delete modifiedReq.bids.ad_unit_id - adapter.callBids(modifiedReq); - sinon.assert.notCalled(adloader.loadScript); - }); - it('does not make a request if ad sizes are incorrect', () => { - let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); - modifiedReq.bids[0].sizes = [[500, 500]]; - adapter.callBids(modifiedReq); - sinon.assert.notCalled(adloader.loadScript); - }); - it('does not make a request if ad sizes are invalid format', () => { - let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); - modifiedReq.bids[0].sizes = 1234; - adapter.callBids(modifiedReq); - sinon.assert.notCalled(adloader.loadScript); - }); - - describe('requesting an ad', () => { - afterEach(() => { - sandbox.restore(); - }) - it('makes a request if valid sizes are provided (nested array)', () => { - makeSuccessfulRequest(adapter); - sinon.assert.calledOnce(adloader.loadScript); - expect(adloader.loadScript.firstCall.args[0]).to.contain( - 'ads04.tapsense.com' - ); - }); - it('handles a singles array for size parameter', () => { - let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); - modifiedReq.bids[0].sizes = [320, 50]; - adapter.callBids(modifiedReq); - expect(adloader.loadScript.firstCall.args[0]).to.contain( - 'ads04.tapsense.com' - ); - }); - it('handles a string for size parameter', () => { - let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); - modifiedReq.bids[0].sizes = '320x50'; - adapter.callBids(modifiedReq); - expect(adloader.loadScript.firstCall.args[0]).to.contain( - 'ads04.tapsense.com' - ); - }); - it('handles a string with multiple sizes for size parameter', () => { - let modifiedReq = duplicate(DEFAULT_BIDDER_REQUEST); - modifiedReq.bids[0].sizes = '320x50,500x500'; - adapter.callBids(modifiedReq); - expect(adloader.loadScript.firstCall.args[0]).to.contain( - 'ads04.tapsense.com' - ); - }); - it('appends bid params as a query string when requesting ad', () => { - makeSuccessfulRequest(adapter); - sinon.assert.calledOnce(adloader.loadScript); - expect(adloader.loadScript.firstCall.args[0]).to.match( - /ufid=thisisaufid&/ - ); - expect(adloader.loadScript.firstCall.args[0]).to.match( - /refer=thisisarefer&/ - ); - expect(adloader.loadScript.firstCall.args[0]).to.match( - /version=[^&]+&/ - ); - expect(adloader.loadScript.firstCall.args[0]).to.match( - /jsonp=1&/ - ); - expect(adloader.loadScript.firstCall.args[0]).to.match( - /ad_unit_id=thisisanadunitid&/ - ); - expect(adloader.loadScript.firstCall.args[0]).to.match( - /device_id=thisisadeviceid&/ - ); - expect(adloader.loadScript.firstCall.args[0]).to.match( - /lat=thisislat&/ - ); - expect(adloader.loadScript.firstCall.args[0]).to.match( - /long=thisisalong&/ - ); - expect(adloader.loadScript.firstCall.args[0]).to.match( - /user=thisisanidfa&/ - ); - expect(adloader.loadScript.firstCall.args[0]).to.match( - /price_floor=0\.01&/ - ); - expect(adloader.loadScript.firstCall.args[0]).to.match( - /callback=$$PREBID_GLOBAL$$\.tapsense\.callback_with_price_.+&/ - ); - }) - }) - }); - - describe('generateCallback', () => { - beforeEach(() => { - sandbox.stub(adloader, 'loadScript'); - }); - afterEach(() => { - sandbox.restore(); - }); - it('generates callback in namespaced object with correct bidder id', () => { - makeSuccessfulRequest(adapter); - expect($$PREBID_GLOBAL$$.tapsense.callback_with_price_2b211418dd0575).to.exist.and.to.be.a('function'); - }) - }); - - describe('response', () => { - beforeEach(() => { - sandbox.stub(bidmanager, 'addBidResponse'); - sandbox.stub(adloader, 'loadScript'); - let bids = makeSuccessfulRequest(adapter); - sandbox.stub(utils, 'getBidRequest', (id) => { - return bids.find((item) => { return item.bidId === id }); - }) - }); - afterEach(() => { - sandbox.restore(); - }); - describe('successful response', () => { - beforeEach(() => { - $$PREBID_GLOBAL$$.tapsense.callback_with_price_2b211418dd0575(SUCCESSFUL_RESPONSE, 1.2); - }); - it('called the bidmanager and registers a bid', () => { - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(1); - }); - it('should have the correct placementCode', () => { - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('thisisatest'); - }); - }); - describe('unsuccessful response', () => { - beforeEach(() => { - $$PREBID_GLOBAL$$.tapsense.callback_with_price_2b211418dd0575(UNSUCCESSFUL_RESPONSE, 1.2); - }) - it('should call the bidmanger and register an invalid bid', () => { - sinon.assert.calledOnce(bidmanager.addBidResponse); - expect(bidmanager.addBidResponse.firstCall.args[1].getStatusCode()).to.equal(2); - }); - it('should have the correct placementCode', () => { - expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('thisisatest'); - }) - }); - describe('no response/timeout', () => { - it('should not register any bids', () => { - sinon.assert.notCalled(bidmanager.addBidResponse); - }) - }); - describe('edge cases', () => { - it('does not register a bid if no price is supplied', () => { - sandbox.stub(utils, 'logMessage'); - $$PREBID_GLOBAL$$.tapsense.callback_with_price_2b211418dd0575(SUCCESSFUL_RESPONSE); - sinon.assert.notCalled(bidmanager.addBidResponse); - }); - }); - }); -}) diff --git a/test/spec/modules/thoughtleadrBidAdapter_spec.js b/test/spec/modules/thoughtleadrBidAdapter_spec.js deleted file mode 100644 index 3cd2a49e888..00000000000 --- a/test/spec/modules/thoughtleadrBidAdapter_spec.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; -var chai_1 = require('chai'); -var ta = require('modules/thoughtleadrBidAdapter'); -var bidfactory = require('src/bidfactory'); -var ajax = require('src/ajax'); -var Adapter = ta; - -describe('thoughtleadr adapter tests', function () { - var sandbox; - var adapter; - var request; - var createBid; - var ajaxMock; - - before(function () { - sandbox = sinon.sandbox.create(); - }); - - beforeEach(function () { - createBid = sandbox.spy(bidfactory, 'createBid'); - adapter = new Adapter(); - request = { - bidderCode: 'thoughtleadr', - bids: [{ - bidder: 'thoughtleadr', - placementCode: 'abc-123', - sizes: [[300, 250], [400, 400]], - params: { - placementId: 'test-placement', - }, - }], - }; - }); - - afterEach(function () { - sandbox.restore(); - }); - - describe('handleBids', function () { - it('should filter invalid bids', function () { - request.bids.unshift({ - bidder: 'thoughtleadr', - placementCode: 'abc-123', - sizes: [[300, 250], [400, 400]], - params: {}, - }); - request.bids.push({ - bidder: 'thoughtleadr', - placementCode: 'abc-123', - sizes: [[300, 250], [400, 400]], - params: { - incorrectParam: 123, - }, - }); - var requestPlacement = sinon.spy(adapter, 'requestPlacement'); - adapter.callBids(request); - chai_1.expect(requestPlacement.callCount).to.be.equal(1); - chai_1.expect(requestPlacement.getCall(0).args[0]).to.be.equal(request.bids[1]); - }); - }); - - describe('requestPlacement', function () { - it('should request header-bid.json', function () { - ajaxMock = sandbox.stub(ajax, 'ajax', function (url, cb) { - cb(JSON.stringify({ - header_bid_token: 'asd', - media_type: 'article', - amount: 2 - })); - }); - adapter.callBids(request); - chai_1.expect(ajaxMock.callCount).to.be.equal(1); - chai_1.expect(ajaxMock.firstCall.args[0]).to.contains( - '//a.thoughtleadr.com/v4/test-placement/header-bid.json?uid='); - chai_1.expect(createBid.firstCall.args[0]).to.be.equal(1); - }); - - it('should request header-bid.json without bids', function () { - ajaxMock = sandbox.stub(ajax, 'ajax', function (url, cb) { - cb(JSON.stringify({})); - }); - - adapter.callBids(request); - chai_1.expect(ajaxMock.callCount).to.be.equal(1); - chai_1.expect(ajaxMock.firstCall.args[0]).to.contains( - '//a.thoughtleadr.com/v4/test-placement/header-bid.json?uid='); - chai_1.expect(createBid.firstCall.args[0]).to.be.equal(2); - }); - - it('should sync cookies', function () { - ajaxMock = sandbox.stub(ajax, 'ajax', function (url, cb) { - cb(JSON.stringify({ - header_bid_token: 'asd', - media_type: 'article', - amount: 2, - cookie_syncs: ['<script src="/path/to/script"></script>'] - })); - }); - adapter.callBids(request); - - var element = document.getElementById('tldr-cookie-sync-div'); - var iframes = element.getElementsByTagName('iframe'); - chai_1.expect(iframes.length).to.be.equal(1); - - chai_1.expect(iframes[0].contentDocument.body.innerHTML).to.be.equal('<script src="/path/to/script"></script>'); - }); - }); -}); diff --git a/test/spec/modules/tremorBidAdapter_spec.js b/test/spec/modules/tremorBidAdapter_spec.js deleted file mode 100644 index c09c1112b3e..00000000000 --- a/test/spec/modules/tremorBidAdapter_spec.js +++ /dev/null @@ -1,173 +0,0 @@ -import {expect} from 'chai'; -import Adapter from 'modules/tremorBidAdapter'; -import bidmanager from 'src/bidmanager'; - -const AD_CODE = 'ssp-!demo!-lufip'; -const SUPPLY_CODE = 'ssp-%21demo%21-rm6rh'; -const SIZES = [640, 480]; -const REQUEST = { - 'code': 'video1', - 'sizes': [640, 480], - 'mediaType': 'video', - 'bids': [{ - 'bidder': 'tremor', - 'params': { - 'mediaId': 'MyCoolVideo', - 'mediaUrl': '', - 'mediaTitle': '', - 'contentLength': '', - 'floor': '', - 'efloor': '', - 'custom': '', - 'categories': '', - 'keywords': '', - 'blockDomains': '', - 'c2': '', - 'c3': '', - 'c4': '', - 'skip': '', - 'skipmin': '', - 'skipafter': '', - 'delivery': '', - 'placement': '', - 'videoMinBitrate': '', - 'videoMaxBitrate': '' - } - }] -}; - -const RESPONSE = { - 'cur': 'USD', - 'id': '3dba13e35f3d42f998bc7e65fd871889', - 'seatbid': [{ - 'seat': 'TremorVideo', - 'bid': [{ - 'adomain': [], - 'price': 0.50000, - 'id': '3dba13e35f3d42f998bc7e65fd871889', - 'adm': '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<VAST version="2.0"> <Ad id="defaultText"> <InLine> <AdSystem version="1.0">Tremor Video</AdSystem> <AdTitle>Test MP4 Creative</AdTitle> <Error><![CDATA[https://events.tremorhub.com/diag?rid=3dba13e35f3d42f998bc7e65fd871889&req_ts=1503951950395&pbid=47376&seatid=60858&aid=348453&asid=null&lid=null&rid=3dba13e35f3d42f998bc7e65fd871889&rtype=VAST_ERR&vastError=[ERRORCODE]&sec=true&adcode=ssp-!demo!-lufip&seatId=60858&pbid=47376&brid=141046&sid=149810&sdom=console.tremorhub.com&aid=348453]]></Error>\n<Impression id="TV"><![CDATA[https://events.tremorhub.com/evt?rid=3dba13e35f3d42f998bc7e65fd871889&req_ts=1503951950395&pbid=47376&seatid=60858&aid=348453&asid=null&lid=null&tuid=97e0d10a4b504700b578e4f7d22cac35&evt=IMP&tvssa=false]]></Impression>\n<Impression/> <Creatives> <Creative> <Linear> <Duration><![CDATA[ 00:00:30 ]]></Duration> <AdParameters><![CDATA[ &referer=- ]]></AdParameters> <MediaFiles> <MediaFile delivery="progressive" height="360" type="video/mp4" width="640"> <![CDATA[https://cdn.tremorhub.com/adUnitTest/tremor_video_test_ad_30sec_640x360.mp4]]> </MediaFile> </MediaFiles> <TrackingEvents>\n<Tracking event="start"><![CDATA[https://events.tremorhub.com/evt?rid=3dba13e35f3d42f998bc7e65fd871889&req_ts=1503951950395&pbid=47376&seatid=60858&aid=348453&asid=null&lid=null&tuid=97e0d10a4b504700b578e4f7d22cac35&evt=start&vastcrtype=linear&crid=]]></Tracking>\n<Tracking event="firstQuartile"><![CDATA[https://events.tremorhub.com/evt?rid=3dba13e35f3d42f998bc7e65fd871889&req_ts=1503951950395&pbid=47376&seatid=60858&aid=348453&asid=null&lid=null&tuid=97e0d10a4b504700b578e4f7d22cac35&evt=firstQuartile&vastcrtype=linear&crid=]]></Tracking>\n<Tracking event="midpoint"><![CDATA[https://events.tremorhub.com/evt?rid=3dba13e35f3d42f998bc7e65fd871889&req_ts=1503951950395&pbid=47376&seatid=60858&aid=348453&asid=null&lid=null&tuid=97e0d10a4b504700b578e4f7d22cac35&evt=midpoint&vastcrtype=linear&crid=]]></Tracking>\n<Tracking event="thirdQuartile"><![CDATA[https://events.tremorhub.com/evt?rid=3dba13e35f3d42f998bc7e65fd871889&req_ts=1503951950395&pbid=47376&seatid=60858&aid=348453&asid=null&lid=null&tuid=97e0d10a4b504700b578e4f7d22cac35&evt=thirdQuartile&vastcrtype=linear&crid=]]></Tracking>\n<Tracking event="complete"><![CDATA[https://events.tremorhub.com/evt?rid=3dba13e35f3d42f998bc7e65fd871889&req_ts=1503951950395&pbid=47376&seatid=60858&aid=348453&asid=null&lid=null&tuid=97e0d10a4b504700b578e4f7d22cac35&evt=complete&vastcrtype=linear&crid=]]></Tracking>\n</TrackingEvents> <VideoClicks>\n<ClickTracking id="TV"><![CDATA[https://events.tremorhub.com/evt?rid=3dba13e35f3d42f998bc7e65fd871889&req_ts=1503951950395&pbid=47376&seatid=60858&aid=348453&asid=null&lid=null&tuid=97e0d10a4b504700b578e4f7d22cac35&evt=click&vastcrtype=linear&crid=]]></ClickTracking>\n</VideoClicks> </Linear> </Creative> </Creatives> <Extensions/> </InLine> </Ad>\n</VAST>\n', - 'impid': '1' - }] - }] -}; - -describe('TremorBidAdapter', () => { - let adapter; - - beforeEach(() => adapter = new Adapter()); - - describe('request function', () => { - let xhr; - let requests; - - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); - }); - - afterEach(() => xhr.restore()); - - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - - it('requires paramters to make request', () => { - adapter.callBids({}); - expect(requests).to.be.empty; - }); - - it('requires adCode && supplyCode', () => { - let backup = REQUEST.bids[0].params; - REQUEST.bids[0].params = {adCode: AD_CODE}; - adapter.callBids(REQUEST); - expect(requests).to.be.empty; - REQUEST.bids[0].params = backup; - }); - - it('requires proper sizes to make a bid request', () => { - let backupBid = REQUEST; - backupBid.sizes = []; - adapter.callBids(backupBid); - expect(requests).to.be.empty; - }); - - it('generates a proper ad call URL', () => { - REQUEST.bids[0].params.adCode = AD_CODE; - REQUEST.bids[0].params.supplyCode = SUPPLY_CODE; - REQUEST.bids[0].sizes = SIZES; - adapter.callBids(REQUEST); - const requestUrl = requests[0].url; - let srcPageURl = ('&srcPageUrl=' + encodeURIComponent(document.location.href)); - expect(requestUrl).to.equal('http://ssp-%21demo%21-rm6rh.ads.tremorhub.com/ad/tag?adCode=ssp-!demo!-lufip&playerWidth=640&playerHeight=480' + srcPageURl + '&mediaId=MyCoolVideo&fmt=json'); - }); - - it('generates a proper ad call URL given a different size format', () => { - REQUEST.bids[0].params.adCode = AD_CODE; - REQUEST.bids[0].params.supplyCode = SUPPLY_CODE; - REQUEST.bids[0].sizes = [SIZES]; - adapter.callBids(REQUEST); - const requestUrl = requests[0].url; - let srcPageURl = ('&srcPageUrl=' + encodeURIComponent(document.location.href)); - expect(requestUrl).to.equal('http://ssp-%21demo%21-rm6rh.ads.tremorhub.com/ad/tag?adCode=ssp-!demo!-lufip&playerWidth=640&playerHeight=480' + srcPageURl + '&mediaId=MyCoolVideo&fmt=json'); - }); - }); - - describe('response handler', () => { - let server; - - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); - }); - - afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); - }); - - it('registers bids', () => { - server.respondWith(JSON.stringify(RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('cpm', 0.50000); - }); - - it('handles nobid responses', () => { - server.respondWith(JSON.stringify({ - 'cur': 'USD', - 'id': 'ff83ce7e00df41c9bce79b651afc7c51', - 'seatbid': [] - })); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); - }); - - it('handles JSON.parse errors', () => { - server.respondWith(''); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); - }); - }); -}); diff --git a/test/spec/modules/trionBidAdapter_spec.js b/test/spec/modules/trionBidAdapter_spec.js index 5a9c92ef91e..559122a2772 100644 --- a/test/spec/modules/trionBidAdapter_spec.js +++ b/test/spec/modules/trionBidAdapter_spec.js @@ -1,31 +1,26 @@ -import { expect } from 'chai'; -import TrionAdapter from 'modules/trionBidAdapter'; -import bidmanager from 'src/bidmanager'; +import {expect} from 'chai'; import * as utils from 'src/utils'; +import {spec, acceptPostMessage, getStorageData, setStorageData} from 'modules/trionBidAdapter'; const CONSTANTS = require('src/constants.json'); const adloader = require('src/adloader'); const PLACEMENT_CODE = 'ad-tag'; -const BID_REQUEST_BASE_URL = 'https://in-appadvertising.com/api/bidRequest?'; -const USER_SYNC_URL = 'https://in-appadvertising.com/api/userSync.js'; - -const TRION_BID_REQUEST = { - start: new Date().getTime(), - bidderCode: 'trion', - bids: [ - { - bidder: 'trion', - params: { - pubId: '1', - sectionId: '2' - }, - placementCode: PLACEMENT_CODE, - sizes: [[300, 250], [300, 600]], - bidId: 'test-bid-id' - } - ] +const BID_REQUEST_BASE_URL = 'https://in-appadvertising.com/api/bidRequest'; + +const TRION_BID = { + bidder: 'trion', + params: { + pubId: '1', + sectionId: '2' + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600]], + bidId: 'test-bid-id', + bidRequest: 'test-bid-request' }; +const TRION_BID_REQUEST = [TRION_BID]; + const TRION_BID_RESPONSE = { bidId: 'test-bid-id', sizes: [[300, 250], [300, 600]], @@ -44,231 +39,179 @@ describe('Trion adapter tests', () => { let adapter; beforeEach(() => { - adapter = new TrionAdapter(); + // adapter = trionAdapter.createNew(); sinon.stub(document.body, 'appendChild'); }); afterEach(() => document.body.appendChild.restore()); - it('should exist and be a function', function () { - expect($$PREBID_GLOBAL$$.handleTrionCB).to.exist.and.to.be.a('function'); - }); + describe('isBidRequestValid', () => { + it('should return true with correct params', () => { + expect(spec.isBidRequestValid(TRION_BID)).to.equal(true); + }); - describe('request function', () => { - let spyLoadScript; + it('should return false when params are missing', () => { + TRION_BID.params = {}; - beforeEach(() => { - spyLoadScript = sinon.spy(adloader, 'loadScript'); - window.TRION_INT = { - int_t: -1 + expect(spec.isBidRequestValid(TRION_BID)).to.equal(false); + TRION_BID.params = { + pubId: '1', + sectionId: '2' }; }); - afterEach(() => { - spyLoadScript.restore(); - delete window.TRION_INT; - }); + it('should return false when pubId is missing', () => { + TRION_BID.params = { + sectionId: '2' + }; - it('callBids exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); + expect(spec.isBidRequestValid(TRION_BID)).to.equal(false); + TRION_BID.params = { + pubId: '1', + sectionId: '2' + }; }); - it('should not call loadscript when inputting with empty params', function () { - adapter.callBids({}); - sinon.assert.notCalled(spyLoadScript); + it('should return false when sectionId is missing', () => { + TRION_BID.params = { + pubId: '1' + }; + + expect(spec.isBidRequestValid(TRION_BID)).to.equal(false); + TRION_BID.params = { + pubId: '1', + sectionId: '2' + }; }); + }); - it('should include the base bidrequest url', function () { - adapter.callBids(TRION_BID_REQUEST); + describe('buildRequests', () => { + it('should return bids requests with empty params', () => { + let bidRequests = spec.buildRequests([]); + expect(bidRequests.length).to.equal(0); + }); - sinon.assert.calledOnce(spyLoadScript); + it('should include the base bidrequest url', () => { + let bidRequests = spec.buildRequests(TRION_BID_REQUEST); - let bidUrl = spyLoadScript.getCall(0).args[0]; + let bidUrl = bidRequests[0].url; expect(bidUrl).to.include(BID_REQUEST_BASE_URL); }); - it('should call loadscript with the correct required params', function () { - adapter.callBids(TRION_BID_REQUEST); - - sinon.assert.calledOnce(spyLoadScript); + it('should call buildRequests with the correct required params', () => { + let bidRequests = spec.buildRequests(TRION_BID_REQUEST); - let bidUrl = spyLoadScript.getCall(0).args[0]; - expect(bidUrl).to.include('pubId=1'); - expect(bidUrl).to.include('sectionId=2'); - expect(bidUrl).to.include('sizes=300x250,300x600'); + let bidUrlParams = bidRequests[0].data; + expect(bidUrlParams).to.include('pubId=1'); + expect(bidUrlParams).to.include('sectionId=2'); + expect(bidUrlParams).to.include('sizes=300x250,300x600'); }); - it('should call loadscript with the correct optional params', function () { - let params = TRION_BID_REQUEST.bids[0].params; + it('should call buildRequests with the correct optional params', () => { + let params = TRION_BID_REQUEST[0].params; params.re = 1; + let bidRequests = spec.buildRequests(TRION_BID_REQUEST); - adapter.callBids(TRION_BID_REQUEST); - - sinon.assert.calledOnce(spyLoadScript); - - let bidUrl = spyLoadScript.getCall(0).args[0]; - expect(bidUrl).to.include('re=1'); - expect(bidUrl).to.include(utils.getTopWindowUrl()); - expect(bidUrl).to.include('slot=' + PLACEMENT_CODE); + let bidUrlParams = bidRequests[0].data; + expect(bidUrlParams).to.include('re=1'); + expect(bidUrlParams).to.include(utils.getTopWindowUrl()); delete params.re; }); - - describe('user sync', () => { - beforeEach(() => { - delete window.TRION_INT; - delete window.TR_INT_T; - }); - - it('user sync is called', () => { - adapter.callBids(TRION_BID_REQUEST); - sinon.assert.calledWith(spyLoadScript, USER_SYNC_URL); - }); - - it('user sync tag is included in bid url', () => { - window.TRION_INT = { - campaigns: [ - 'campaign1', - 'campaign2' - ], - int_t: 'int_t' - }; - let userTag = encodeURIComponent(JSON.stringify(window.TRION_INT)); - adapter.callBids(TRION_BID_REQUEST); - - let bidUrl = spyLoadScript.getCall(0).args[0]; - expect(bidUrl).to.include(userTag); - }); - - it('user sync tag is included in bid url and includes the correct int_t', () => { - window.TRION_INT = { - campaigns: [ - 'campaign1', - 'campaign2' - ] - }; - let int_t = 'test'; - let expectedObject = { - campaigns: [ - 'campaign1', - 'campaign2' - ], - int_t: int_t - }; - window.TR_INT_T = int_t; - let userTag = encodeURIComponent(JSON.stringify(expectedObject)); - adapter.callBids(TRION_BID_REQUEST); - - let bidUrl = spyLoadScript.getCall(0).args[0]; - expect(bidUrl).to.include(userTag); - }); - - it('user sync tag variable int_t cannot be changed once set', () => { - window.TRION_INT = { - campaigns: [ - 'campaign1', - 'campaign2' - ] - }; - let int_t = 'test'; - let expectedObject = { - campaigns: [ - 'campaign1', - 'campaign2' - ], - int_t: int_t - }; - window.TR_INT_T = int_t; - let userTag = encodeURIComponent(JSON.stringify(expectedObject)); - adapter.callBids(TRION_BID_REQUEST); - window.TR_INT_T = 'bad'; - let bidUrl = spyLoadScript.getCall(0).args[0]; - - expect(bidUrl).to.include(userTag); - expect(bidUrl).to.not.include('bad'); - }); - }); }); - describe('response handler', () => { - beforeEach(() => { - sinon.stub(bidmanager, 'addBidResponse'); + describe('interpretResponse', () => { + it('when there is no response do not bid', () => { + let response = spec.interpretResponse(null, {bidRequest: TRION_BID}); + expect(response).to.deep.equal([]); }); - afterEach(() => { - bidmanager.addBidResponse.restore(); - }); + it('when place bid is returned as false', () => { + TRION_BID_RESPONSE.result.placeBid = false; + let response = spec.interpretResponse({body: TRION_BID_RESPONSE}, {bidRequest: TRION_BID}); - it('when there is no response do not bid', function () { - $$PREBID_GLOBAL$$._bidsRequested.push(TRION_BID_REQUEST); - $$PREBID_GLOBAL$$.handleTrionCB(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); - }); + expect(response).to.deep.equal([]); - it('when place bid is returned as false', function () { - TRION_BID_RESPONSE.result.placeBid = false; - $$PREBID_GLOBAL$$._bidsRequested.push(TRION_BID_REQUEST); - $$PREBID_GLOBAL$$.handleTrionCB(TRION_BID_RESPONSE); - sinon.assert.calledOnce(bidmanager.addBidResponse); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); TRION_BID_RESPONSE.result.placeBid = true; }); - it('when no cpm is in the response', function () { + it('when no cpm is in the response', () => { TRION_BID_RESPONSE.result.cpm = 0; - $$PREBID_GLOBAL$$._bidsRequested.push(TRION_BID_REQUEST); - $$PREBID_GLOBAL$$.handleTrionCB(TRION_BID_RESPONSE); - sinon.assert.calledOnce(bidmanager.addBidResponse); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + let response = spec.interpretResponse({body: TRION_BID_RESPONSE}, {bidRequest: TRION_BID}); + expect(response).to.deep.equal([]); TRION_BID_RESPONSE.result.cpm = 1; }); - it('when no ad is in the response', function () { + it('when no ad is in the response', () => { TRION_BID_RESPONSE.result.ad = null; - $$PREBID_GLOBAL$$._bidsRequested.push(TRION_BID_REQUEST); - $$PREBID_GLOBAL$$.handleTrionCB(TRION_BID_RESPONSE); - sinon.assert.calledOnce(bidmanager.addBidResponse); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + let response = spec.interpretResponse({body: TRION_BID_RESPONSE}, {bidRequest: TRION_BID}); + expect(response).to.deep.equal([]); TRION_BID_RESPONSE.result.ad = 'test'; }); - it('bid response is formatted correctly', function () { - $$PREBID_GLOBAL$$._bidsRequested.push(TRION_BID_REQUEST); - $$PREBID_GLOBAL$$.handleTrionCB(TRION_BID_RESPONSE); - const placementCode = bidmanager.addBidResponse.firstCall.args[0]; - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(placementCode).to.equal(PLACEMENT_CODE); - expect(response.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(response.bidderCode).to.equal('trion'); - }); - - it('height and width are appropriately set', function () { + it('height and width are appropriately set', () => { let bidWidth = '1'; let bidHeight = '2'; TRION_BID_RESPONSE.result.width = bidWidth; TRION_BID_RESPONSE.result.height = bidHeight; - $$PREBID_GLOBAL$$._bidsRequested.push(TRION_BID_REQUEST); - $$PREBID_GLOBAL$$.handleTrionCB(TRION_BID_RESPONSE); - const placementCode = bidmanager.addBidResponse.firstCall.args[0]; - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response.width).to.equal(bidWidth); - expect(response.height).to.equal(bidHeight); + let response = spec.interpretResponse({body: TRION_BID_RESPONSE}, {bidRequest: TRION_BID}); + expect(response[0].width).to.equal(bidWidth); + expect(response[0].height).to.equal(bidHeight); TRION_BID_RESPONSE.result.width = '300'; TRION_BID_RESPONSE.result.height = '250'; }); - it('cpm is properly set and transformed to cents', function () { + it('cpm is properly set and transformed to cents', () => { let bidCpm = 2; TRION_BID_RESPONSE.result.cpm = bidCpm * 100; - $$PREBID_GLOBAL$$._bidsRequested.push(TRION_BID_REQUEST); - $$PREBID_GLOBAL$$.handleTrionCB(TRION_BID_RESPONSE); - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response.cpm).to.equal(bidCpm); + let response = spec.interpretResponse({body: TRION_BID_RESPONSE}, {bidRequest: TRION_BID}); + expect(response[0].cpm).to.equal(bidCpm); TRION_BID_RESPONSE.result.cpm = 100; }); }); + + describe('getUserSyncs', () => { + const USER_SYNC_URL = 'https://in-appadvertising.com/api/userSync.html'; + const BASE_KEY = '_trion_'; + + beforeEach(() => { + delete window.TR_INT_T; + }); + + it('trion int is included in bid url', () => { + window.TR_INT_T = 'test_user_sync'; + let userTag = encodeURIComponent(window.TR_INT_T); + let bidRequests = spec.buildRequests(TRION_BID_REQUEST); + let bidUrlParams = bidRequests[0].data; + + expect(bidUrlParams).to.include(userTag); + }); + + it('should register trion user script', () => { + let syncs = spec.getUserSyncs({iframeEnabled: true}); + let url = utils.getTopWindowUrl(); + let pubId = 1; + let sectionId = 2; + let syncString = `?p=${pubId}&s=${sectionId}&u=${url}`; + expect(syncs[0]).to.deep.equal({type: 'iframe', url: USER_SYNC_URL + syncString}); + }); + + it('should except posted messages from user sync script', () => { + let testId = 'testId'; + let message = BASE_KEY + 'userId=' + testId; + setStorageData(BASE_KEY + 'int_t', null); + acceptPostMessage({data: message}); + let newKey = getStorageData(BASE_KEY + 'int_t'); + expect(newKey).to.equal(testId); + }); + + it('should not try to post messages not from trion', () => { + let testId = 'testId'; + let badId = 'badId'; + let message = 'Not Trion: userId=' + testId; + setStorageData(BASE_KEY + 'int_t', badId); + acceptPostMessage({data: message}); + let newKey = getStorageData(BASE_KEY + 'int_t'); + expect(newKey).to.equal(badId); + }); + }); }); diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js deleted file mode 100644 index 95658883fd0..00000000000 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ /dev/null @@ -1,226 +0,0 @@ -import {expect} from 'chai'; -import Adapter from '../../../modules/tripleliftBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import adLoader from '../../../src/adloader'; -import {parse as parseURL} from '../../../src/url'; - -describe('triplelift adapter', () => { - let bidsRequestedOriginal; - let adapter; - let sandbox; - - const bidderRequest = { - bidderCode: 'triplelift', - bids: [ - { - bidId: 'bidId1', - bidder: 'triplelift', - placementCode: 'foo', - sizes: [[728, 90]], - params: { - inventoryCode: 'codeA' - } - }, - { - bidId: 'bidId2', - bidder: 'triplelift', - placementCode: 'bar', - sizes: [[300, 600]], - params: { - inventoryCode: 'codeB', - floor: 1 - } - } - ] - }; - - beforeEach(() => { - bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; - $$PREBID_GLOBAL$$._bidsRequested = []; - - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); - - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; - }); - - describe('callBids', () => { - let firstBidScriptURL; - let secondBidScriptURL; - - beforeEach(() => { - sandbox.stub(adLoader, 'loadScript'); - adapter.callBids(bidderRequest); - - firstBidScriptURL = adLoader.loadScript.firstCall.args[0]; - secondBidScriptURL = adLoader.loadScript.secondCall.args[0] - }); - - it('should load a script for each bid request', () => { - sinon.assert.calledTwice(adLoader.loadScript); - - let route = 'http://tlx.3lift.com/header/auction?'; - expect(firstBidScriptURL).to.contain(route); - expect(secondBidScriptURL).to.contain(route); - - let firstScriptParams = parseURL(firstBidScriptURL).search; - expect(firstScriptParams).to.have.property('callback', '$$PREBID_GLOBAL$$.TLCB'); - expect(firstScriptParams).to.have.property('callback_id', 'bidId1'); - expect(firstScriptParams).to.have.property('inv_code', 'codeA'); - expect(firstScriptParams).to.have.property('size', '728x90'); - expect(firstScriptParams).to.have.property('referrer'); - - let secondScriptParams = parseURL(secondBidScriptURL).search; - expect(secondScriptParams).to.have.property('callback', '$$PREBID_GLOBAL$$.TLCB'); - expect(secondScriptParams).to.have.property('callback_id', 'bidId2'); - expect(secondScriptParams).to.have.property('inv_code', 'codeB'); - expect(secondScriptParams).to.have.property('size', '300x600'); - expect(secondScriptParams).to.have.property('floor', '1'); - expect(secondScriptParams).to.have.property('referrer'); - }); - }); - - describe('TLCB', () => { - it('should exist and be a function', () => { - expect($$PREBID_GLOBAL$$.TLCB).to.exist.and.to.be.a('function'); - }); - }); - - describe('add bids to the manager', () => { - let firstBid; - let secondBid; - - beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - - // respond - let bidderReponse1 = { - 'ad': '<script></script>', - 'callback_id': 'bidId1', - 'cpm': 0.20, - 'height': 90, - 'iurl': '', - 'width': 728 - }; - - let bidderReponse2 = { - 'ad': '<script></script>', - 'callback_id': 'bidId2', - 'cpm': 0.30, - 'height': 600, - 'iurl': '', - 'width': 300, - 'deal_id': 'dealA' - }; - - $$PREBID_GLOBAL$$.TLCB(bidderReponse1); - $$PREBID_GLOBAL$$.TLCB(bidderReponse2); - - firstBid = bidManager.addBidResponse.firstCall.args[1]; - secondBid = bidManager.addBidResponse.secondCall.args[1]; - }); - - it('should add a bid object for each bid', () => { - sinon.assert.calledTwice(bidManager.addBidResponse); - }); - - it('should pass the correct placement code as first param', () => { - let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; - let secondPlacementCode = bidManager.addBidResponse.secondCall.args[0]; - - expect(firstPlacementCode).to.eql('foo'); - expect(secondPlacementCode).to.eql('bar'); - }); - - it('should include the bid request bidId as the adId', () => { - expect(firstBid).to.have.property('adId', 'bidId1'); - expect(secondBid).to.have.property('adId', 'bidId2'); - }); - - it('should have a good statusCode', () => { - expect(firstBid.getStatusCode()).to.eql(1); - expect(secondBid.getStatusCode()).to.eql(1); - }); - - it('should add the CPM to the bid object', () => { - expect(firstBid).to.have.property('cpm', 0.20); - expect(secondBid).to.have.property('cpm', 0.30); - }); - - it('should add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'triplelift'); - expect(secondBid).to.have.property('bidderCode', 'triplelift'); - }); - - it('should include the ad on the bid object', () => { - expect(firstBid).to.have.property('ad'); - expect(secondBid).to.have.property('ad'); - }); - - it('should include the size on the bid object', () => { - expect(firstBid).to.have.property('width', 728); - expect(firstBid).to.have.property('height', 90); - expect(secondBid).to.have.property('width', 300); - expect(secondBid).to.have.property('height', 600); - }); - - it('should include the dealId on the bid object if present', () => { - expect(firstBid).to.have.property('dealId', undefined); - expect(secondBid).to.have.property('dealId', 'dealA'); - }); - }); - - describe('add empty bids if no bid returned', () => { - let firstBid; - let secondBid; - - beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - - // respond - let bidderReponse1 = {'status': 'no_bid', 'callback_id': 'bidId1'}; - let bidderReponse2 = {'status': 'no_bid', 'callback_id': 'bidId2'}; - - $$PREBID_GLOBAL$$.TLCB(bidderReponse1); - $$PREBID_GLOBAL$$.TLCB(bidderReponse2); - - firstBid = bidManager.addBidResponse.firstCall.args[1]; - secondBid = bidManager.addBidResponse.secondCall.args[1]; - }); - - it('should add a bid object for each bid', () => { - sinon.assert.calledTwice(bidManager.addBidResponse); - }); - - it('should include the bid request bidId as the adId', () => { - expect(firstBid).to.have.property('adId', 'bidId1'); - expect(secondBid).to.have.property('adId', 'bidId2'); - }); - - it('should have an error statusCode', () => { - expect(firstBid.getStatusCode()).to.eql(2); - expect(secondBid.getStatusCode()).to.eql(2); - }); - - it('should pass the correct placement code as first param', () => { - let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; - let secondPlacementCode = bidManager.addBidResponse.secondCall.args[0]; - - expect(firstPlacementCode).to.eql('foo'); - expect(secondPlacementCode).to.eql('bar'); - }); - - it('should add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'triplelift'); - expect(secondBid).to.have.property('bidderCode', 'triplelift'); - }); - }); -}); diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 7208ebef343..6149545d59f 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -1,234 +1,293 @@ -describe('trustx adapter tests', function () { - var expect = require('chai').expect; - var assert = require('chai').assert; - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - - var adapter = require('modules/trustxBidAdapter'); - var bidmanager = require('src/bidmanager'); - var adLoader = require('src/adloader'); - var utils = require('src/utils'); - window.$$PREBID_GLOBAL$$ = window.$$PREBID_GLOBAL$$ || {}; - - if (typeof (pbjs) === 'undefined') { - var pbjs = window.$$PREBID_GLOBAL$$; - } - let stubLoadScript; - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - afterEach(function () { - stubLoadScript.restore(); - }); - var logErrorSpy; - beforeEach(function () { - logErrorSpy = sinon.spy(utils, 'logError'); - }); - afterEach(function () { - logErrorSpy.restore(); +import { expect } from 'chai'; +import { spec } from 'modules/trustxBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('TrustXAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); }); - describe('creation of request url', function () { - if (typeof (pbjs._bidsRequested) === 'undefined') { - pbjs._bidsRequested = []; - } - it('should fix parameter name', function () { - var params = { - bidderCode: 'trustx', - bids: [ - { - bidder: 'trustx', - params: { - uid: 5 - }, - placementCode: 'div-1' - }, - { - bidder: 'trustx', - params: { - uid: 6 - }, - placementCode: 'div-1' - }, - { - bidder: 'trustx', - params: {}, - placementCode: 'div-2' - }, - { - bidder: 'trustx', - params: { - uid: 6, - test: true - }, - placementCode: 'div-3' - }, - { - bidder: 'trustx', - placementCode: 'div-4' - } - ] + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'trustx', + 'params': { + 'uid': '44' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + 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', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 }; - adapter().callBids(params); - var bidUrl = stubLoadScript.getCall(0).args[0]; - sinon.assert.calledWith(stubLoadScript, bidUrl); - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - var generatedCallback = '$$PREBID_GLOBAL$$.trustx_callback_wrapper_5_6'; - expect(parsedBidUrl.hostname).to.equal('sofia.trustx.org'); - expect(parsedBidUrl.pathname).to.equal('/hb'); - expect(parsedBidUrlQueryString).to.have.property('auids').and.to.equal('5,6'); - expect(parsedBidUrlQueryString).to.have.property('u').and.to.equal(location.href); - expect(parsedBidUrlQueryString).to.have.property('cb').and.to.equal(generatedCallback); + expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); - describe('validate incoming params', function () { - if (typeof (pbjs._bidsRequested) === 'undefined') { - pbjs._bidsRequested = []; - } - it('has no correct item in config', function () { - var params = { - bidderCode: 'trustx', - bids: [ - { - bidder: 'trustx', - params: {}, - placementCode: 'div-1' - }, - { - bidder: 'trustx', - placementCode: 'div-1' - } - ] - }; - adapter().callBids(params); - sinon.assert.notCalled(stubLoadScript); - expect(logErrorSpy.getCall(0).args[0]).to.equal('Uids should be not empty'); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '45' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', () => { + const request = spec.buildRequests([bidRequests[0]]); + 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', 'net'); + expect(payload).to.have.property('auids', '43'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('auids must not be duplicated', () => { + const request = spec.buildRequests(bidRequests); + 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', 'net'); + expect(payload).to.have.property('auids', '43,45'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', () => { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests); + 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', '43,45'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', () => { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests); + 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', 'net'); + expect(payload).to.have.property('auids', '43,45'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; }); }); - describe('handling of the callback response', function () { - if (typeof (pbjs._bidsReceived) === 'undefined') { - pbjs._bidsReceived = []; - } - if (typeof (pbjs._bidsRequested) === 'undefined') { - pbjs._bidsRequested = []; - } - if (typeof (pbjs._adsReceived) === 'undefined') { - pbjs._adsReceived = []; - } - var params = { - bidderCode: 'trustx', - bids: [ + + describe('interpretResponse', () => { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '<div>test content 1</div>', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '<div>test content 2</div>', 'auid': 44, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 45, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '<div>test content 4</div>', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', () => { + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + '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': 43, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '<div>test content 1</div>', + 'bidderCode': 'trustx', + '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', () => { + const bidRequests = [ { - bidder: 'trustx', - params: { - uid: 5 + 'bidder': 'trustx', + 'params': { + 'uid': '43' }, - placementCode: '/19968336/header-bid-tag-0' + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', }, { - bidder: 'trustx', - params: { - uid: 6 + 'bidder': 'trustx', + 'params': { + 'uid': '44' }, - placementCode: '/19968336/header-bid-tag-1' + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', }, { - bidder: 'trustx', - params: { - uid: 42 + 'bidder': 'trustx', + 'params': { + 'uid': '43' }, - placementCode: '/19968336/header-bid-tag-2' + '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': 43, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '<div>test content 1</div>', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 43, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '<div>test content 1</div>', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, }, { - bidder: 'trustx', - params: { - uid: 43 + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 44, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '<div>test content 2</div>', + 'bidderCode': 'trustx', + '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', () => { + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '45' }, - placementCode: '/19968336/header-bid-tag-3' + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', }, { - bidder: 'trustx', - params: { - uid: 44 + 'bidder': 'trustx', + 'params': { + 'uid': '46' }, - placementCode: '/19968336/header-bid-tag-4' + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', }, { - bidder: 'trustx', - params: { - uid: 45 + 'bidder': 'trustx', + 'params': { + 'uid': '50' }, - placementCode: '/19968336/header-bid-tag-5' + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '300bfeb0d7183bb', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', } - ] - }; - it('callback function should exist', function () { - adapter().callBids(params); - expect(pbjs['trustx_callback_wrapper_5_6_42_43_44_45']) - .to.exist.and.to.be.a('function'); - }); - it('bidmanager.addBidResponse should be called with correct arguments', function () { - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - adapter().callBids(params); - var adUnits = []; - var unit = {}; - unit.bids = params.bids; - unit.code = '/19968336/header-bid-tag'; - adUnits.push(unit); - if (typeof (pbjs._bidsRequested) === 'undefined') { - pbjs._bidsRequested = [params]; - } else { - pbjs._bidsRequested.push(params); - } - pbjs.adUnits = adUnits; - var response = { - seatbid: [ - {bid: [{price: 1.15, adm: '<div>test content 1</div>', auid: 5, h: 90, w: 728}], seat: '1'}, - {bid: [{price: 0, auid: 6, h: 250, w: 300}], seat: '1'}, - {bid: [{price: 0, adm: '<div>test content 3</div>', h: 250, w: 300}], seat: '1'}, - undefined, - {bid: [], seat: '1'}, - {seat: '1'}, - {bid: [{price: 0, adm: '<div>test content 7</div>', auid: 46, h: 250, w: 300}], seat: '1'} - ] - }; - pbjs['trustx_callback_wrapper_5_6_42_43_44_45'](response); - var bidPlacementCode1 = stubAddBidResponse.getCall(1).args[0]; - var bidObject1 = stubAddBidResponse.getCall(1).args[1]; - var bidPlacementCode2 = stubAddBidResponse.getCall(0).args[0]; - var bidObject2 = stubAddBidResponse.getCall(0).args[1]; - var bidPlacementCode3 = stubAddBidResponse.getCall(2).args[0]; - var bidObject3 = stubAddBidResponse.getCall(2).args[1]; - var bidPlacementCode4 = stubAddBidResponse.getCall(3).args[0]; - var bidObject4 = stubAddBidResponse.getCall(3).args[1]; - var bidPlacementCode5 = stubAddBidResponse.getCall(4).args[0]; - var bidObject5 = stubAddBidResponse.getCall(4).args[1]; - var bidPlacementCode6 = stubAddBidResponse.getCall(5).args[0]; - var bidObject6 = stubAddBidResponse.getCall(5).args[1]; - expect(logErrorSpy.getCall(5).args[0]).to.equal('Bid from response has no adm parameter - {"price":0,"auid":6,"h":250,"w":300}'); - expect(logErrorSpy.getCall(4).args[0]).to.equal('Bid from response has no auid parameter - {"price":0,"adm":"<' + 'div>test content 3</' + 'div>","h":250,"w":300}'); - expect(logErrorSpy.getCall(3).args[0]).to.equal('Seatbid array from response has empty item'); - expect(logErrorSpy.getCall(2).args[0]).to.equal('Array of bid objects is empty'); - expect(logErrorSpy.getCall(1).args[0]).to.equal('Seatbid from response has no array of bid objects - {"seat":"1"}'); - expect(logErrorSpy.getCall(0).args[0]).to.equal('Can\'t find placementCode for bid with auid - 46, placementCode is available only for the following uids - 5,6,42,43,44,45'); - expect(bidPlacementCode1).to.equal('/19968336/header-bid-tag-0'); - expect(bidObject1.cpm).to.equal(1.15); - expect(bidObject1.ad).to.equal('<div>test content 1</div>'); - expect(bidObject1.width).to.equal(728); - expect(bidObject1.height).to.equal(90); - expect(bidObject1.getStatusCode()).to.equal(1); - expect(bidObject1.bidderCode).to.equal('trustx'); - expect(bidPlacementCode2).to.equal('/19968336/header-bid-tag-1'); - expect(bidObject2.getStatusCode()).to.equal(2); - expect(bidPlacementCode3).to.equal('/19968336/header-bid-tag-2'); - expect(bidObject3.getStatusCode()).to.equal(2); - expect(bidPlacementCode4).to.equal('/19968336/header-bid-tag-3'); - expect(bidObject4.getStatusCode()).to.equal(2); - expect(bidPlacementCode5).to.equal('/19968336/header-bid-tag-4'); - expect(bidObject5.getStatusCode()).to.equal(2); - expect(bidPlacementCode6).to.equal('/19968336/header-bid-tag-5'); - expect(bidObject6.getStatusCode()).to.equal(2); - stubAddBidResponse.restore(); + ]; + 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/twengaBidAdapter_spec.js b/test/spec/modules/twengaBidAdapter_spec.js deleted file mode 100644 index dcead6c4578..00000000000 --- a/test/spec/modules/twengaBidAdapter_spec.js +++ /dev/null @@ -1,119 +0,0 @@ -describe('twenga adapter tests', function () { - var urlParse = require('url-parse'); - var querystringify = require('querystringify'); - var Adapter = require('modules/twengaBidAdapter'); - var adLoader = require('src/adloader'); - var expect = require('chai').expect; - var bidmanager = require('src/bidmanager'); - var CONSTANTS = require('src/constants.json'); - - var DEFAULT_PARAMS = { - bidderCode: 'twenga', - bids: [{ - bidId: 'tw_abcd1234', - sizes: [[300, 250], [300, 200]], - bidder: 'twenga', - params: { - placementId: 'test', - siteId: 1234, - publisherId: 5678, - currency: 'USD', - bidFloor: 0.5, - country: 'DE' - }, - requestId: 'tw_efgh5678', - placementCode: 'tw_42' - }] - }; - - var BID_RESPONSE = { - result: { - cpm: 10000, - width: 300, - height: 250, - ad: '//rtb.t.c4tw.net', - creative_id: 'test' - }, - callback_uid: 'tw_abcd1234' - }; - - it('sets url parameters', function () { - var stubLoadScript = sinon.stub(adLoader, 'loadScript'); - - (new Adapter()).callBids(DEFAULT_PARAMS); - - var bidUrl = stubLoadScript.getCall(0).args[0]; - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - expect(parsedBidUrl.hostname).to.equal('rtb.t.c4tw.net'); - expect(parsedBidUrl.pathname).to.equal('/Bid'); - - expect(parsedBidUrlQueryString).to.have.property('s').and.to.equal('h'); - expect(parsedBidUrlQueryString).to.have.property('callback').and.to.equal('$$PREBID_GLOBAL$$.handleTwCB'); - expect(parsedBidUrlQueryString).to.have.property('callback_uid').and.to.equal('tw_abcd1234'); - expect(parsedBidUrlQueryString).to.have.property('id').and.to.equal('test'); - - stubLoadScript.restore(); - }); - - var stringToFunction = function (s) { - var scope = global; - var scopeSplit = s.split('.'); - for (var i = 0; i < scopeSplit.length - 1; i++) { - scope = scope[scopeSplit[i]]; - if (scope == undefined) return; - } - return scope[scopeSplit[scopeSplit.length - 1]]; - }; - - it('creates an empty bid response if no bids', function() { - var stubLoadScript = sinon.stub(adLoader, 'loadScript', function(url) { - var bidUrl = stubLoadScript.getCall(0).args[0]; - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - var callback = stringToFunction(parsedBidUrlQueryString.callback); - expect(callback).to.exist.and.to.be.a('function'); - callback(undefined); - }); - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - (new Adapter()).callBids(DEFAULT_PARAMS); - - expect(stubAddBidResponse.getCall(0)).to.be.null; - - stubAddBidResponse.restore(); - stubLoadScript.restore(); - }); - - it('creates a bid response if bid is returned', function() { - var stubLoadScript = sinon.stub(adLoader, 'loadScript', function(url) { - var bidUrl = stubLoadScript.getCall(0).args[0]; - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - $$PREBID_GLOBAL$$._bidsRequested - .push({ bidderCode: DEFAULT_PARAMS.bidderCode, - bids: [{ bidId: parsedBidUrlQueryString.callback_uid, - placementCode: DEFAULT_PARAMS.bids[0].placementCode }]}); - - var callback = stringToFunction(parsedBidUrlQueryString.callback); - expect(callback).to.exist.and.to.be.a('function'); - callback(BID_RESPONSE); - }); - var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - - (new Adapter()).callBids(DEFAULT_PARAMS); - - var bidResponseAd = stubAddBidResponse.getCall(0).args[1]; - - expect(bidResponseAd).to.have.property('cpm').and.to.equal(BID_RESPONSE.result.cpm / 10000); - expect(bidResponseAd).to.have.property('adUrl').and.to.equal(BID_RESPONSE.result.ad); - expect(bidResponseAd).to.have.property('width').and.to.equal(BID_RESPONSE.result.width); - expect(bidResponseAd).to.have.property('height').and.to.equal(BID_RESPONSE.result.height); - - stubAddBidResponse.restore(); - stubLoadScript.restore(); - }); -}); diff --git a/test/spec/modules/ucfunnelBidAdapter_spec.js b/test/spec/modules/ucfunnelBidAdapter_spec.js index d8ddfc041b6..152c7c39b1e 100644 --- a/test/spec/modules/ucfunnelBidAdapter_spec.js +++ b/test/spec/modules/ucfunnelBidAdapter_spec.js @@ -1,110 +1,108 @@ import { expect } from 'chai'; -import Adapter from 'modules/ucfunnelBidAdapter'; -import adapterManager from 'src/adaptermanager'; -import bidManager from 'src/bidmanager'; -import CONSTANTS from 'src/constants.json'; - -describe('ucfunnel adapter tests', function () { - let sandbox; - const adUnit = { // TODO CHANGE - code: 'ucfunnel', - sizes: [[300, 250]], - bids: [{ - bidder: 'ucfunnel', - params: { - adid: 'test-ad-83444226E44368D1E32E49EEBE6D29', - width: 300, - height: 250 - } - }] - }; - - const response = { - ad_id: 'ad-83444226E44368D1E32E49EEBE6D29', - adm: '<html style="height:100%"><body style="width:300px;height: 100%;padding:0;margin:0 auto;"><div style="width:100%;height:100%;display:table;"><div style="width:100%;height:100%;display:table-cell;text-align:center;vertical-align:middle;"><a href="//www.ucfunnel.com/" target="_blank"><img src="//cdn.aralego.net/ucfad/house/ucf/AdGent-300x250.jpg" width="300px" height="250px" align="middle" style="border:none"></a></div></div></body></html>', - cpm: 0.01, +import { spec } from 'modules/ucfunnelBidAdapter'; + +const URL = '//hb.aralego.com/header'; +const BIDDER_CODE = 'ucfunnel'; +const validBidReq = { + bidder: BIDDER_CODE, + params: { + adid: 'test-ad-83444226E44368D1E32E49EEBE6D29' + }, + sizes: [[300, 250]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746', +}; + +const invalidBidReq = { + bidder: BIDDER_CODE, + params: { + adid: 123456789 + }, + sizes: [[300, 250]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}; + +const bidReq = [{ + bidder: BIDDER_CODE, + params: { + adid: 'test-ad-83444226E44368D1E32E49EEBE6D29' + }, + sizes: [[300, 250]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}]; + +const validBidRes = { + ad_id: 'ad-83444226E44368D1E32E49EEBE6D29', + adm: '<html style="height:100%"><body style="width:300px;height: 100%;padding:0;margin:0 auto;"><div style="width:100%;height:100%;display:table;"><div style="width:100%;height:100%;display:table-cell;text-align:center;vertical-align:middle;"><a href="//www.ucfunnel.com/" target="_blank"><img src="//cdn.aralego.net/ucfad/house/ucf/AdGent-300x250.jpg" width="300px" height="250px" align="middle" style="border:none"></a></div></div></body></html>', + cpm: 0.01, + height: 250, + width: 300 +}; + +const bidResponse = validBidRes; + +const bidResArray = [ + validBidRes, + { + ad: '', + bidRequestId: '263be71e91dd9d', + cpm: 100, + adId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, height: 250, - width: 300 - }; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('ucfunnel callBids validation', () => { - let bids, - server; - - beforeEach(() => { - bids = []; - server = sinon.fakeServer.create(); + ttl: 360 + }, + { + ad: '<div>Hello</div>', + bidRequestId: '', + cpm: 0, + adId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + } +]; - sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { - bids.push(bid); - }); +describe('ucfunnel Adapter', () => { + describe('request', () => { + it('should validate bid request', () => { + expect(spec.isBidRequestValid(validBidReq)).to.equal(true); }); - - afterEach(() => { - server.restore(); + it('should not validate incorrect bid request', () => { + expect(spec.isBidRequestValid(invalidBidReq)).to.equal(false); }); + }); + describe('build request', () => { + it('Verify bid request', () => { + const request = spec.buildRequests(bidReq); + expect(request[0].method).to.equal('GET'); + expect(request[0].url).to.equal(URL); + expect(request[0].data).to.match(new RegExp(`${bidReq[0].params.adid}`)); + }); + }); - let adapter = adapterManager.bidderRegistry['ucfunnel']; - - it('Valid bid-request', () => { - sandbox.stub(adapter, 'callBids'); - adapterManager.callBids({ - adUnits: [clone(adUnit)] - }); - - let bidderRequest = adapter.callBids.getCall(0).args[0]; - - expect(bidderRequest).to.have.property('bids') - .that.is.an('array') - .with.lengthOf(1); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .to.have.property('bidder', 'ucfunnel'); - - expect(bidderRequest).to.have.deep.property('bids[0]') - .with.property('sizes') - .that.is.an('array') - .with.lengthOf(1) - .that.deep.equals(adUnit.sizes); - expect(bidderRequest).to.have.deep.property('bids[0]') - .with.property('params') - .to.have.property('adid', 'test-ad-83444226E44368D1E32E49EEBE6D29'); - expect(bidderRequest).to.have.deep.property('bids[0]') - .with.property('params') - .to.have.property('width', 300); + describe('interpretResponse', () => { + it('should build bid array', () => { + const request = spec.buildRequests(bidReq); + const result = spec.interpretResponse({body: bidResponse}, request[0]); + expect(result.length).to.equal(1); }); - it('Valid bid-response', () => { - server.respondWith(JSON.stringify( - response - )); - adapterManager.callBids({ - adUnits: [clone(adUnit)] - }); - server.respond(); + it('should have all relevant fields', () => { + const request = spec.buildRequests(bidReq); + const result = spec.interpretResponse({body: bidResponse}, request[0]); + const bid = result[0]; - expect(bids).to.be.lengthOf(1); - expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); - expect(bids[0].bidderCode).to.equal('ucfunnel'); - expect(bids[0].width).to.equal(300); - expect(bids[0].height).to.equal(250); - expect(bids[0].cpm).to.equal(0.01); + expect(bid.requestId).to.equal('263be71e91dd9d'); + expect(bid.cpm).to.equal(0.01); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); }); }); }); - -function clone(obj) { - try { - return JSON.parse(JSON.stringify(obj)); - } catch (e) { - return {}; - } -} diff --git a/test/spec/modules/underdogmediaBidAdapter_spec.js b/test/spec/modules/underdogmediaBidAdapter_spec.js index 249111be6ea..5dc2a65399f 100644 --- a/test/spec/modules/underdogmediaBidAdapter_spec.js +++ b/test/spec/modules/underdogmediaBidAdapter_spec.js @@ -1,122 +1,241 @@ -import Adapter from '../../../modules/underdogmediaBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import adloader from '../../../src/adloader'; - -import { - expect -} from 'chai'; - -describe('underdogmedia adapter test', () => { - let adapter; - let server; - - // The third bid here is an invalid site id and should return a 'no-bid'. - - var bidderRequest = { - bidderCode: 'underdogmedia', - bids: [{ - bidder: 'underdogmedia', - adUnitCode: 'foo', - sizes: [ - [728, 90] - ], - params: { - siteId: '10272' - } - }, - { - bidder: 'underdogmedia', - adUnitCode: 'bar', - sizes: [ - [300, 250] - ], - params: { - siteId: '10272', - subId: 'TEST_SUBID' - } - }, - { - bidder: 'underdogmedia', - adUnitCode: 'nothing', - sizes: [160, 600], - params: { - siteId: '31337' - } - } - ] - }; - var response = { - 'mids': [{ - 'width': 728, - 'notification_url': '//udmserve.net/notification_url', - 'height': 90, - 'cpm': 2.5, - 'ad_code_html': 'Ad HTML for site ID 10272 size 728x90' - }, - { - 'width': 300, - 'notification_url': '//udmserve.net/notification_url', - 'height': 250, - 'cpm': 2.0, - 'ad_code_html': 'Ad HTML for site ID 10272 size 300x250' - } - ] - }; +import { expect } from 'chai'; +import { spec } from 'modules/underdogmediaBidAdapter'; + +describe('UnderdogMedia adapter', () => { + let bidRequests; beforeEach(() => { - adapter = new Adapter(); + bidRequests = [ + { + bidder: 'underdogmedia', + params: { + siteId: 12143 + }, + adUnitCode: '/19968336/header-bid-tag-1', + sizes: [[300, 250], [300, 600], [728, 90], [160, 600], [320, 50]], + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; }); - afterEach(() => {}); + describe('implementation', () => { + describe('for requests', () => { + it('should accept valid bid', () => { + let validBid = { + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + sizes: [[300, 250], [300, 600]] + }; + const isValid = spec.isBidRequestValid(validBid); - describe('adding bids to the manager', () => { - let firstBid; - let secondBid; - let thirdBid; + expect(isValid).to.equal(true); + }); - beforeEach(() => { - sinon.stub(bidManager, 'addBidResponse'); - sinon.stub(adloader, 'loadScript'); + it('should reject invalid bid missing sizes', () => { + let invalidBid = { + bidder: 'underdogmedia', + params: { + siteId: '12143', + } + }; + const isValid = spec.isBidRequestValid(invalidBid); - adapter.callBids(bidderRequest); - $$PREBID_GLOBAL$$.handleUnderdogMediaCB(JSON.parse(JSON.stringify(response))); - firstBid = bidManager.addBidResponse.firstCall.args[1]; - secondBid = bidManager.addBidResponse.secondCall.args[1]; - thirdBid = bidManager.addBidResponse.thirdCall.args[1]; - }); + expect(isValid).to.equal(false); + }); - afterEach(() => { - bidManager.addBidResponse.restore(); - adloader.loadScript.restore(); - }); + it('should reject invalid bid missing siteId', () => { + let invalidBid = { + bidder: 'underdogmedia', + params: {}, + sizes: [[300, 250], [300, 600]] + }; + const isValid = spec.isBidRequestValid(invalidBid); - it('will add a bid object for each bid', () => { - sinon.assert.calledThrice(bidManager.addBidResponse); - }); + expect(isValid).to.equal(false); + }); - it('will add the ad html to the bid object', () => { - expect(firstBid).to.have.property('ad').includes('Ad HTML for site ID 10272 size 728x90'); - expect(secondBid).to.have.property('ad').includes('Ad HTML for site ID 10272 size 300x250').and.includes('TEST_SUBID'); - expect(thirdBid).to.not.have.property('ad'); - }); + it('request data should contain sid', () => { + let bidRequests = [ + { + bidId: '3c9408cdbf2f68', + sizes: [[300, 250]], + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + } + ]; + const request = spec.buildRequests(bidRequests); - it('will have the right size attached', () => { - expect(firstBid).to.have.property('width', 728); - expect(firstBid).to.have.property('height', 90); - expect(secondBid).to.have.property('width', 300); - expect(secondBid).to.have.property('height', 250); - }); + expect(request.data).to.have.string('sid=12143'); + }); + + it('request data should contain sizes', () => { + let bidRequests = [ + { + bidId: '3c9408cdbf2f68', + sizes: [[300, 250], [728, 90]], + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + } + ]; + const request = spec.buildRequests(bidRequests); - it('will add the CPM to the bid object', () => { - expect(firstBid).to.have.property('cpm', 2.5); - expect(secondBid).to.have.property('cpm', 2.0); - expect(thirdBid).to.not.have.property('cpm'); + expect(request.data).to.have.string('sizes=300x250,728x90'); + }); }); - it('will add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'underdogmedia'); - expect(secondBid).to.have.property('bidderCode', 'underdogmedia'); - expect(thirdBid).to.have.property('bidderCode', 'underdogmedia'); + describe('bid responses', () => { + it('should return complete bid response', () => { + let serverResponse = { + body: { + mids: [ + { + ad_code_html: 'ad_code_html', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }, + { + ad_code_html: 'ad_code_html', + cpm: 2.5, + height: '250', + mid: '32633', + notification_url: 'notification_url', + tid: '2', + width: '300' + }, + ] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(2); + + expect(bids[0].bidderCode).to.equal('underdogmedia'); + expect(bids[0].cpm).to.equal(2.5); + expect(bids[0].width).to.equal('160'); + expect(bids[0].height).to.equal('600'); + expect(bids[0].ad).to.have.length.above(1); + expect(bids[0].creativeId).to.equal('32634'); + expect(bids[0].currency).to.equal('USD'); + }); + + it('should return empty bid response if mids empty', () => { + let serverResponse = { + body: { + mids: [] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on incorrect size', () => { + let serverResponse = { + body: { + mids: [ + { + ad_code_html: 'ad_code_html', + cpm: 2.5, + height: '123', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + } + ] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on 0 cpm', () => { + let serverResponse = { + body: { + mids: [ + { + ad_code_html: 'ad_code_html', + cpm: 0, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + } + ] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response if no ad in response', () => { + let serverResponse = { + body: { + mids: [ + { + ad_code_html: '', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + } + ] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + + it('ad html string should contain the notification urls', () => { + let serverResponse = { + body: { + mids: [ + { + ad_code_html: 'ad_cod_html', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + } + ] + } + }; + const request = spec.buildRequests(bidRequests); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids[0].ad).to.have.string('notification_url'); + expect(bids[0].ad).to.have.string(';style=adapter'); + }); }); }); }); diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js new file mode 100644 index 00000000000..d86a1dc5735 --- /dev/null +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -0,0 +1,138 @@ +import { expect } from 'chai'; +import { spec } from 'modules/undertoneBidAdapter'; + +const URL = '//hb.undertone.com/hb'; +const BIDDER_CODE = 'undertone'; +const validBidReq = { + bidder: BIDDER_CODE, + params: { + placementId: '10433394', + publisherId: 12345 + }, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746', +}; + +const invalidBidReq = { + bidder: BIDDER_CODE, + params: { + placementId: '123456789' + }, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}; + +const bidReq = [{ + bidder: BIDDER_CODE, + params: { + placementId: '10433394', + publisherId: 12345 + }, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}]; + +const validBidRes = { + ad: '<div>Hello</div>', + publisherId: 12345, + bidRequestId: '263be71e91dd9d', + adId: 15, + cpm: 100, + nCampaignId: 2, + creativeId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 +}; + +const bidResponse = [validBidRes]; + +const bidResArray = [ + validBidRes, + { + ad: '', + bidRequestId: '263be71e91dd9d', + cpm: 100, + adId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + }, + { + ad: '<div>Hello</div>', + bidRequestId: '', + cpm: 0, + adId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + } +]; + +describe('Undertone Adapter', () => { + describe('request', () => { + it('should validate bid request', () => { + expect(spec.isBidRequestValid(validBidReq)).to.equal(true); + }); + it('should not validate incorrect bid request', () => { + expect(spec.isBidRequestValid(invalidBidReq)).to.equal(undefined); + }); + }); + describe('build request', () => { + it('should send request to correct url via POST', () => { + const request = spec.buildRequests(bidReq); + const domain = null; + 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 have all relevant fields', () => { + const request = spec.buildRequests(bidReq); + const bid = JSON.parse(request.data)['x-ut-hb-params'][0]; + expect(bid.bidRequestId).to.equal('263be71e91dd9d'); + expect(bid.sizes.length > 0).to.equal(true); + expect(bid.placementId).to.equal('10433394'); + expect(bid.publisherId).to.equal(12345); + expect(bid.params).to.be.an('object'); + }); + }); + + describe('interpretResponse', () => { + it('should build bid array', () => { + let result = spec.interpretResponse({body: bidResponse}); + expect(result.length).to.equal(1); + }); + + it('should have all relevant fields', () => { + const result = spec.interpretResponse({body: bidResponse}); + const bid = result[0]; + + expect(bid.requestId).to.equal('263be71e91dd9d'); + expect(bid.cpm).to.equal(100); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal(15); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(360); + }); + + it('should return empty array when response is incorrect', () => { + expect(spec.interpretResponse({body: {}}).length).to.equal(0); + expect(spec.interpretResponse({body: []}).length).to.equal(0); + }); + + it('should only use valid bid responses', () => { + expect(spec.interpretResponse({ body: bidResArray }).length).to.equal(1); + }); + }); +}); diff --git a/test/spec/modules/unrulyBidAdapter_spec.js b/test/spec/modules/unrulyBidAdapter_spec.js index 067f3ea46d0..e4eaa35d662 100644 --- a/test/spec/modules/unrulyBidAdapter_spec.js +++ b/test/spec/modules/unrulyBidAdapter_spec.js @@ -1,47 +1,18 @@ /* globals describe, it, beforeEach, afterEach, sinon */ import { expect } from 'chai' -import bidfactory from 'src/bidfactory' -import bidmanager from 'src/bidmanager' import * as utils from 'src/utils' import { STATUS } from 'src/constants' +import { VIDEO } from 'src/mediaTypes' import { Renderer } from 'src/Renderer' -import createUnrulyAdapter from 'modules/unrulyBidAdapter' +import { adapter } from 'modules/unrulyBidAdapter' describe('UnrulyAdapter', () => { - function createBidRequestBid({ placementCode }) { - return { - 'bidder': 'unruly', - 'params': { - 'uuid': '74544e00-d43b-4f3a-a799-69d22ce979ce', - 'siteId': 794599, - 'placementId': '5768085' - }, - 'placementCode': placementCode, - 'mediaTypes': { video: { context: 'outstream' } }, - 'transactionId': '62890707-3770-497c-a3b8-d905a2d0cb98', - 'sizes': [ - 640, - 480 - ], - 'bidId': '23b86d8f6335ce', - 'bidderRequestId': '1d5b7474eb5416', - 'requestId': '406fe12b-fa3b-4bd3-b3c8-043951b4dac1' - } - } - - function createParams(...bids) { - return { - 'bidderCode': 'unruly', - 'requestId': '406fe12b-fa3b-4bd3-b3c8-043951b4dac1', - 'bidderRequestId': '1d5b7474eb5416', - 'bids': bids, - 'start': 1495794517251, - 'auctionStart': 1495794517250, - 'timeout': 3000 - } - } - - function createOutStreamExchangeBid({ placementCode, statusCode = 1 }) { + function createOutStreamExchangeBid({ + adUnitCode = 'placement2', + statusCode = 1, + bidId = 'foo', + vastUrl = 'https://targeting.unrulymedia.com/in_article?uuid=74544e00-d43b-4f3a-a799-69d22ce979ce&supported_mime_type=application/javascript&supported_mime_type=video/mp4&tj=%7B%22site%22%3A%7B%22lang%22%3A%22en-GB%22%2C%22ref%22%3A%22%22%2C%22page%22%3A%22http%3A%2F%2Fdemo.unrulymedia.com%2FinArticle%2Finarticle_nypost_upbeat%2Ftravel_magazines.html%22%2C%22domain%22%3A%22demo.unrulymedia.com%22%7D%2C%22user%22%3A%7B%22profile%22%3A%7B%22quantcast%22%3A%7B%22segments%22%3A%5B%7B%22id%22%3A%22D%22%7D%2C%7B%22id%22%3A%22T%22%7D%5D%7D%7D%7D%7D&video_width=618&video_height=347' + }) { return { 'ext': { 'statusCode': statusCode, @@ -50,229 +21,186 @@ describe('UnrulyAdapter', () => { 'config': {}, 'url': 'https://video.unrulymedia.com/native/prebid-loader.js' }, - 'placementCode': placementCode - }, - 'cpm': 20, - 'bidderCode': 'unruly', - 'width': 323, - 'vastUrl': 'https://targeting.unrulymedia.com/in_article?uuid=74544e00-d43b-4f3a-a799-69d22ce979ce&supported_mime_type=application/javascript&supported_mime_type=video/mp4&tj=%7B%22site%22%3A%7B%22lang%22%3A%22en-GB%22%2C%22ref%22%3A%22%22%2C%22page%22%3A%22http%3A%2F%2Fdemo.unrulymedia.com%2FinArticle%2Finarticle_nypost_upbeat%2Ftravel_magazines.html%22%2C%22domain%22%3A%22demo.unrulymedia.com%22%7D%2C%22user%22%3A%7B%22profile%22%3A%7B%22quantcast%22%3A%7B%22segments%22%3A%5B%7B%22id%22%3A%22D%22%7D%2C%7B%22id%22%3A%22T%22%7D%5D%7D%7D%7D%7D&video_width=618&video_height=347', - 'bidId': 'foo', - 'height': 323 - } - } - - function createInStreamExchangeBid({ placementCode, statusCode = 1 }) { - return { - 'ext': { - 'statusCode': statusCode, - 'placementCode': placementCode + 'adUnitCode': adUnitCode }, 'cpm': 20, 'bidderCode': 'unruly', 'width': 323, - 'vastUrl': 'https://targeting.unrulymedia.com/in_article?uuid=74544e00-d43b-4f3a-a799-69d22ce979ce&supported_mime_type=application/javascript&supported_mime_type=video/mp4&tj=%7B%22site%22%3A%7B%22lang%22%3A%22en-GB%22%2C%22ref%22%3A%22%22%2C%22page%22%3A%22http%3A%2F%2Fdemo.unrulymedia.com%2FinArticle%2Finarticle_nypost_upbeat%2Ftravel_magazines.html%22%2C%22domain%22%3A%22demo.unrulymedia.com%22%7D%2C%22user%22%3A%7B%22profile%22%3A%7B%22quantcast%22%3A%7B%22segments%22%3A%5B%7B%22id%22%3A%22D%22%7D%2C%7B%22id%22%3A%22T%22%7D%5D%7D%7D%7D%7D&video_width=618&video_height=347', - 'bidId': 'foo', + 'vastUrl': vastUrl, + 'bidId': bidId, 'height': 323 } } - function createExchangeResponse(...bids) { - return { - 'bids': bids - } - } + const createExchangeResponse = (...bids) => ({ + body: { bids } + }); - let adapter - let server - let sandbox - let fakeRenderer + let sandbox; + let fakeRenderer; beforeEach(() => { - adapter = createUnrulyAdapter() - adapter.exchangeUrl = 'http://localhost:9000/prebid' - - sandbox = sinon.sandbox.create() - sandbox.stub(bidmanager, 'addBidResponse') - sandbox.stub(bidfactory, 'createBid') - sandbox.stub(utils, 'logError') + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'logError'); + sandbox.stub(Renderer, 'install'); fakeRenderer = { setRender: sinon.stub() - } - - sandbox.stub(Renderer, 'install') + }; Renderer.install.returns(fakeRenderer) - - server = sinon.fakeServer.create() - }) + }); afterEach(() => { - sandbox.restore() - server.restore() + sandbox.restore(); delete parent.window.unruly - }) - - describe('callBids', () => { - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function') + }); + + it('should expose Unruly Bidder code', () => { + expect(adapter.code).to.equal('unruly') + }); + + it('should contain the VIDEO mediaType', function () { + expect(adapter.supportedMediaTypes).to.deep.equal([ VIDEO ]) + }); + + describe('isBidRequestValid', () => { + it('should be a function', () => { + expect(typeof adapter.isBidRequestValid).to.equal('function') + }); + + it('should return false if bid is falsey', () => { + expect(adapter.isBidRequestValid()).to.be.false; + }); + + it('should return true if bid.mediaType is "video"', () => { + const mockBid = { mediaType: 'video' }; + + expect(adapter.isBidRequestValid(mockBid)).to.be.true; + }); + + it('should return true if bid.mediaTypes.video.context is "outstream"', () => { + const mockBid = { + mediaTypes: { + video: { + context: 'outstream' + } + } + }; + + expect(adapter.isBidRequestValid(mockBid)).to.be.true; + }); + }); + + describe('buildRequests', () => { + it('should be a function', () => { + expect(typeof adapter.buildRequests).to.equal('function'); + }); + it('should return an object', () => { + const mockBidRequests = ['mockBid']; + expect(typeof adapter.buildRequests(mockBidRequests)).to.equal('object') + }); + it('should return a server request with a valid exchange url', () => { + const mockBidRequests = ['mockBid']; + expect(adapter.buildRequests(mockBidRequests).url).to.equal('https://targeting.unrulymedia.com/prebid') + }); + it('should return a server request with method === POST', () => { + const mockBidRequests = ['mockBid']; + expect(adapter.buildRequests(mockBidRequests).method).to.equal('POST'); + }); + it('should ensure contentType is `application/json`', function () { + const mockBidRequests = ['mockBid']; + expect(adapter.buildRequests(mockBidRequests).options).to.deep.equal({ + contentType: 'application/json' + }); + }); + it('should return a server request with valid payload', () => { + const mockBidRequests = ['mockBid']; + expect(adapter.buildRequests(mockBidRequests).data).to.deep.equal({bidRequests: mockBidRequests}) }) - - it('requires bids to make request', () => { - adapter.callBids({}) - expect(server.requests).to.be.empty - }) - - it('requires at least one bid to make request', () => { - adapter.callBids({ bids: [] }) - expect(server.requests).to.be.empty + }); + + describe('interpretResponse', () => { + it('should be a function', () => { + expect(typeof adapter.interpretResponse).to.equal('function'); + }); + it('should return empty array when serverResponse is undefined', () => { + expect(adapter.interpretResponse()).to.deep.equal([]); + }); + it('should return empty array when serverResponse has no bids', () => { + const mockServerResponse = { body: { bids: [] } }; + expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([]) + }); + it('should return array of bids when receive a successful response from server', () => { + const mockExchangeBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); + const mockServerResponse = createExchangeResponse(mockExchangeBid); + expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([ + { + requestId: 'mockBidId', + cpm: 20, + width: 323, + height: 323, + vastUrl: 'https://targeting.unrulymedia.com/in_article?uuid=74544e00-d43b-4f3a-a799-69d22ce979ce&supported_mime_type=application/javascript&supported_mime_type=video/mp4&tj=%7B%22site%22%3A%7B%22lang%22%3A%22en-GB%22%2C%22ref%22%3A%22%22%2C%22page%22%3A%22http%3A%2F%2Fdemo.unrulymedia.com%2FinArticle%2Finarticle_nypost_upbeat%2Ftravel_magazines.html%22%2C%22domain%22%3A%22demo.unrulymedia.com%22%7D%2C%22user%22%3A%7B%22profile%22%3A%7B%22quantcast%22%3A%7B%22segments%22%3A%5B%7B%22id%22%3A%22D%22%7D%2C%7B%22id%22%3A%22T%22%7D%5D%7D%7D%7D%7D&video_width=618&video_height=347', + netRevenue: true, + creativeId: 'mockBidId', + ttl: 360, + currency: 'USD', + renderer: fakeRenderer + } + ]) + }); + + it('should initialize and set the renderer', () => { + expect(Renderer.install).not.to.have.been.called; + expect(fakeRenderer.setRender).not.to.have.been.called; + + const mockReturnedBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); + const mockRenderer = { url: 'value: mockRendererURL' }; + mockReturnedBid.ext.renderer = mockRenderer; + const mockServerResponse = createExchangeResponse(mockReturnedBid); + + adapter.interpretResponse(mockServerResponse); + + expect(Renderer.install).to.have.been.calledOnce; + sinon.assert.calledWithExactly( + Renderer.install, + Object.assign({}, mockRenderer, {callback: sinon.match.func}) + ); + + sinon.assert.calledOnce(fakeRenderer.setRender); + sinon.assert.calledWithExactly(fakeRenderer.setRender, sinon.match.func) + }); + + it('bid is placed on the bid queue when render is called', () => { + const exchangeBid = createOutStreamExchangeBid({ adUnitCode: 'video', vastUrl: 'value: vastUrl' }); + const exchangeResponse = createExchangeResponse(exchangeBid); + + adapter.interpretResponse(exchangeResponse); + + sinon.assert.calledOnce(fakeRenderer.setRender); + fakeRenderer.setRender.firstCall.args[0](); + + expect(window.top).to.have.deep.property('unruly.native.prebid.uq'); + + const uq = window.top.unruly.native.prebid.uq; + const sentRendererConfig = uq[0][1]; + + expect(uq[0][0]).to.equal('render'); + expect(sentRendererConfig.vastUrl).to.equal('value: vastUrl'); + expect(sentRendererConfig.renderer).to.equal(fakeRenderer); + expect(sentRendererConfig.adUnitCode).to.equal('video') }) - it('passes bids through to exchange', () => { - const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) - - adapter.callBids(params) - - expect(server.requests).to.have.length(1) - expect(server.requests[0].url).to.equal('http://localhost:9000/prebid') - - const requestBody = JSON.parse(server.requests[0].requestBody) - expect(requestBody).to.deep.equal({ - 'bidRequests': params.bids - }) - }) + it('should ensure that renderer is placed in Prebid supply mode', () => { + const mockExchangeBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); + const mockServerResponse = createExchangeResponse(mockExchangeBid); - it('creates a bid response using status code from exchange for each bid and passes in the exchange response', () => { - const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) + expect('unruly' in window.parent).to.equal(false); - const exchangeBid1 = createOutStreamExchangeBid({ placementCode: 'placement1' }) - const exchangeBid2 = createOutStreamExchangeBid({ placementCode: 'placement2', statusCode: 2 }) - const exchangeResponse = createExchangeResponse(exchangeBid1, exchangeBid2) + adapter.interpretResponse(mockServerResponse); - server.respondWith(JSON.stringify(exchangeResponse)) - bidfactory.createBid.returns({}) + const supplyMode = window.parent.unruly.native.supplyMode; - adapter.callBids(params) - server.respond() - - sinon.assert.calledTwice(bidfactory.createBid) - sinon.assert.calledWith(bidfactory.createBid, exchangeBid1.ext.statusCode, exchangeResponse.bids[0]) - sinon.assert.calledWith(bidfactory.createBid, exchangeBid2.ext.statusCode, exchangeResponse.bids[1]) - }) - - it('adds the bid response to the bid manager', () => { - const fakeBid = {} - - const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) - const exchangeBid = createOutStreamExchangeBid({ placementCode: 'placement1' }) - const exchangeResponse = createExchangeResponse(exchangeBid) - - server.respondWith(JSON.stringify(exchangeResponse)) - bidfactory.createBid.withArgs(exchangeBid.ext.statusCode).returns(fakeBid) - - adapter.callBids(params) - server.respond() - - sinon.assert.calledOnce(bidmanager.addBidResponse) - sinon.assert.calledWith(bidmanager.addBidResponse, exchangeBid.ext.placementCode, fakeBid) - }) - - describe('on invalid exchange response', () => { - it('should create NO_BID response for each bid request bid', () => { - const bidRequestBid1 = createBidRequestBid({ placementCode: 'placement1' }) - const bidRequestBid2 = createBidRequestBid({ placementCode: 'placement2' }) - const params = createParams(bidRequestBid1, bidRequestBid2) - const expectedBid = { 'some': 'props' } - - server.respondWith('this is not json') - bidfactory.createBid.withArgs(STATUS.NO_BID).returns(expectedBid) - - adapter.callBids(params) - server.respond() - - sinon.assert.calledOnce(utils.logError) - sinon.assert.calledTwice(bidmanager.addBidResponse) - sinon.assert.calledWith(bidmanager.addBidResponse, bidRequestBid1.placementCode, expectedBid) - sinon.assert.calledWith(bidmanager.addBidResponse, bidRequestBid2.placementCode, expectedBid) - }) - }) - - describe('InStream', () => { - it('merges bid response defaults', () => { - const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) - - const fakeBidDefaults = { some: 'default' } - const fakeBid = Object.assign({}, fakeBidDefaults) - - const exchangeBid = createInStreamExchangeBid({ placementCode: 'placement1' }) - const exchangeResponse = createExchangeResponse(exchangeBid) - server.respondWith(JSON.stringify(exchangeResponse)) - - bidfactory.createBid.withArgs(exchangeBid.ext.statusCode).returns(fakeBid) - - adapter.callBids(params) - server.respond() - - sinon.assert.notCalled(Renderer.install) - expect(fakeBid).to.deep.equal(Object.assign( - {}, - fakeBidDefaults, - exchangeBid - )) - }) - }) - - describe('OutStream', () => { - it('merges bid response defaults with exchange bid and renderer', () => { - const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) - - const fakeBidDefaults = { some: 'default' } - const fakeBid = Object.assign({}, fakeBidDefaults) - - const exchangeBid = createOutStreamExchangeBid({ placementCode: 'placement1' }) - const exchangeResponse = createExchangeResponse(exchangeBid) - server.respondWith(JSON.stringify(exchangeResponse)) - - bidfactory.createBid.withArgs(exchangeBid.ext.statusCode).returns(fakeBid) - - const fakeRenderer = {} - Renderer.install.withArgs(Object.assign( - {}, - exchangeBid.ext.renderer, - { callback: sinon.match.func } - )).returns(fakeRenderer) - - adapter.callBids(params) - server.respond() - - expect(fakeBid).to.deep.equal(Object.assign( - {}, - fakeBidDefaults, - exchangeBid, - { renderer: fakeRenderer } - )) - }) - - it('bid is placed on the bid queue when render is called', () => { - const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) - - const fakeBidDefaults = { some: 'default' } - const fakeBid = Object.assign({}, fakeBidDefaults) - - const exchangeBid = createOutStreamExchangeBid({ placementCode: 'placement1' }) - const exchangeResponse = createExchangeResponse(exchangeBid) - server.respondWith(JSON.stringify(exchangeResponse)) - - bidfactory.createBid.withArgs(exchangeBid.ext.statusCode).returns(fakeBid) - - adapter.callBids(params) - server.respond() - - sinon.assert.calledOnce(fakeRenderer.setRender) - fakeRenderer.setRender.firstCall.args[0]() - - expect(window.top).to.have.deep.property('unruly.native.prebid.uq'); - expect(window.top.unruly.native.prebid.uq).to.deep.equal([['render', fakeBid]]) - }) - }) - }) -}) + expect(supplyMode).to.equal('prebid'); + }); + }); +}); diff --git a/test/spec/modules/vertamediaBidAdapter_spec.js b/test/spec/modules/vertamediaBidAdapter_spec.js index 11c29dafad0..271f1f2d04a 100644 --- a/test/spec/modules/vertamediaBidAdapter_spec.js +++ b/test/spec/modules/vertamediaBidAdapter_spec.js @@ -1,141 +1,218 @@ -import { expect } from 'chai'; -import Adapter from 'modules/vertamediaBidAdapter'; -import bidmanager from 'src/bidmanager'; +import {expect} from 'chai'; +import {spec} from 'modules/vertamediaBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; -const ENDPOINT = 'http://rtb.vertamedia.com/hb/?aid=22489&w=640&h=480&domain=localhost'; +const ENDPOINT = '//hb2.vertamedia.com/auction/'; -const REQUEST = { - 'bidderCode': 'vertamedia', - 'requestId': 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', +const DISPLAY_REQUEST = { + 'bidder': 'vertamedia', + 'params': { + 'aid': 12345 + }, 'bidderRequestId': '7101db09af0db2', - 'bids': [ - { - 'bidder': 'vertamedia', - 'params': { - aid: 22489, - placementId: '123456' - }, - 'placementCode': '/19968336/header-bid-tag1', - 'sizes': [640, 480], - 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2', - 'requestId': 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6' - } - ], - 'start': 1469479810130 + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d', + 'sizes': [300, 250] }; -var RESPONSE = { - 'source': { - 'aid': 22489, - 'pubId': 18016, - 'sid': '0' + +const VIDEO_REQUEST = { + 'bidder': 'vertamedia', + 'mediaTypes': { + 'video': {} }, - 'bids': [ - { - 'cmpId': 9541, - 'cpm': 4.5, - 'url': 'http://rtb.vertamedia.com/vast?adid=BFDB9CC0038AD918', - 'cur': 'USD' - } - ] + 'params': { + 'aid': 12345 + }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d', + 'sizes': [[480, 360], [640, 480]] }; -describe('VertamediaAdater', () => { - let adapter; +const SERVER_VIDEO_RESPONSE = { + 'source': {'aid': 12345, 'pubId': 54321}, + 'bids': [{ + 'vastUrl': 'http://rtb.vertamedia.com/vast/?adid=44F2AEB9BFC881B3', + 'requestId': '2e41f65424c87c', + 'url': '44F2AEB9BFC881B3', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 480, + 'cur': 'USD', + 'width': 640, + 'cpm': 0.9 + } + ] +}; +const SERVER_DISPLAY_RESPONSE = { + 'source': {'aid': 12345, 'pubId': 54321}, + 'bids': [{ + 'ad': '<!-- Creative -->', + 'requestId': '2e41f65424c87c', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 250, + 'cur': 'USD', + 'width': 300, + 'cpm': 0.9 + }] +}; - beforeEach(() => adapter = new Adapter()); +const videoBidderRequest = { + bidderCode: 'bidderCode', + bids: [{mediaTypes: {video: {}}, bidId: '2e41f65424c87c'}] +}; - describe('request function', () => { - let xhr; - let requests; +const displayBidderRequest = { + bidderCode: 'bidderCode', + bids: [{bidId: '2e41f65424c87c'}] +}; - beforeEach(() => { - xhr = sinon.useFakeXMLHttpRequest(); - requests = []; - xhr.onCreate = request => requests.push(request); +const videoEqResponse = [{ + vastUrl: 'http://rtb.vertamedia.com/vast/?adid=44F2AEB9BFC881B3', + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'video', + netRevenue: true, + currency: 'USD', + height: 480, + width: 640, + ttl: 3600, + cpm: 0.9 +}]; + +const displayEqResponse = [{ + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'display', + netRevenue: true, + currency: 'USD', + ad: '<!-- Creative -->', + height: 250, + width: 300, + ttl: 3600, + cpm: 0.9 +}]; + +describe('vertamediaBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); }); + }); - afterEach(() => xhr.restore()); + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(VIDEO_REQUEST)).to.equal(12345); + }); - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, VIDEO_REQUEST); + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(undefined); }); + }); - it('requires paramters to make request', () => { - adapter.callBids({}); - expect(requests).to.be.empty; + describe('buildRequests', () => { + let videoBidRequests = [VIDEO_REQUEST]; + let dispalyBidRequests = [DISPLAY_REQUEST]; + + const displayRequest = spec.buildRequests(dispalyBidRequests, {}); + const videoRequest = spec.buildRequests(videoBidRequests, {}); + + it('sends bid request to ENDPOINT via GET', () => { + expect(videoRequest.method).to.equal('GET'); + expect(displayRequest.method).to.equal('GET'); }); - it('requires member && invCode', () => { - let backup = REQUEST.bids[0].params; - REQUEST.bids[0].params = {member: 1234}; - adapter.callBids(REQUEST); - expect(requests).to.be.empty; - REQUEST.bids[0].params = backup; + it('sends bid request to correct ENDPOINT', () => { + expect(videoRequest.url).to.equal(ENDPOINT); + expect(displayRequest.url).to.equal(ENDPOINT); }); - it('sends bid request to ENDPOINT via POST', () => { - adapter.callBids(REQUEST); - expect(requests[0].url).to.equal(ENDPOINT); - expect(requests[0].method).to.equal('GET'); + it('sends correct video bid parameters', () => { + const bid = Object.assign({}, videoRequest.data); + delete bid.domain; + + const eq = { + callbackId: '84ab500420319d', + ad_type: 'video', + aid: 12345, + sizes: '480x360,640x480' + }; + + expect(bid).to.deep.equal(eq); }); - }); - describe('response handler', () => { - let server; + it('sends correct display bid parameters', () => { + const bid = Object.assign({}, displayRequest.data); + delete bid.domain; - beforeEach(() => { - server = sinon.fakeServer.create(); - sinon.stub(bidmanager, 'addBidResponse'); + const eq = { + callbackId: '84ab500420319d', + ad_type: 'display', + aid: 12345, + sizes: '300x250' + }; + + expect(bid).to.deep.equal(eq); }); + }); + + describe('interpretResponse', () => { + let serverResponse; + let bidderRequest; + let eqResponse; afterEach(() => { - server.restore(); - bidmanager.addBidResponse.restore(); + serverResponse = null; + bidderRequest = null; + eqResponse = null; }); - it('registers bids', () => { - server.respondWith(JSON.stringify(RESPONSE)); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('should get correct video bid response', () => { + serverResponse = SERVER_VIDEO_RESPONSE; + bidderRequest = videoBidderRequest; + eqResponse = videoEqResponse; - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('cpm', 4.5); + bidServerResponseCheck(); }); - it('handles nobid responses', () => { - server.respondWith(JSON.stringify({ - aid: 356465468, - w: 640, - h: 480, - domain: 'localhost' - })); - - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); - - const response = bidmanager.addBidResponse.firstCall.args[1]; - expect(response).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); + it('should get correct display bid response', () => { + serverResponse = SERVER_DISPLAY_RESPONSE; + bidderRequest = displayBidderRequest; + eqResponse = displayEqResponse; + + bidServerResponseCheck(); }); - it('handles JSON.parse errors', () => { - server.respondWith(''); + function bidServerResponseCheck() { + const result = spec.interpretResponse({body: serverResponse}, {bidderRequest}); + + expect(result).to.deep.equal(eqResponse); + } + + function nobidServerResponseCheck() { + const noBidServerResponse = {bids: []}; + const noBidResult = spec.interpretResponse({body: noBidServerResponse}, {bidderRequest}); + + expect(noBidResult.length).to.equal(0); + } + + it('handles video nobid responses', () => { + bidderRequest = videoBidderRequest; + + nobidServerResponseCheck(); + }); - adapter.callBids(REQUEST); - server.respond(); - sinon.assert.calledOnce(bidmanager.addBidResponse); + it('handles display nobid responses', () => { + bidderRequest = displayBidderRequest; - expect(bidmanager.addBidResponse.firstCall.args[1]).to.have.property( - 'statusMessage', - 'Bid returned empty or error response' - ); + nobidServerResponseCheck(); }); }); }); diff --git a/test/spec/modules/vertozBidAdapter_spec.js b/test/spec/modules/vertozBidAdapter_spec.js index 87d0ec8c842..a84fc4847f5 100644 --- a/test/spec/modules/vertozBidAdapter_spec.js +++ b/test/spec/modules/vertozBidAdapter_spec.js @@ -1,140 +1,112 @@ -import {expect} from 'chai'; -import {assert} from 'chai'; -import Adapter from '../../../modules/vertozBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import adLoader from '../../../src/adloader'; +import { expect } from 'chai'; +import { spec } from 'modules/vertozBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; -describe('Vertoz Adapter', () => { - let adapter; - let sandbox; - let bidsRequestBuff; - const bidderRequest = { - bidderCode: 'vertoz', - bids: [{ - bidId: 'bidId1', - bidder: 'vertoz', - placementCode: 'foo', - sizes: [ - [300, 250] - ], - params: { - placementId: 'VZ-HB-123' - } - }, { - bidId: 'bidId2', - bidder: 'vertoz', - placementCode: 'bar', - sizes: [ - [728, 90] - ], - params: { - placementId: 'VZ-HB-456' - } - }, { - bidId: 'bidId3', - bidder: 'vertoz', - placementCode: 'coo', - sizes: [ - [300, 600] - ], - params: { - placementId: '' - } - }] - }; +const BASE_URI = '//hb.vrtzads.com/vzhbidder/bid?'; - beforeEach(() => { - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - bidsRequestBuff = $$PREBID_GLOBAL$$._bidsRequested; - $$PREBID_GLOBAL$$._bidsRequested = []; - }); +describe('VertozAdapter', () => { + const adapter = newBidder(spec); - afterEach(() => { - sandbox.restore(); - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestBuff; + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); }); - describe('callBids', () => { - beforeEach(() => { - sandbox.stub(adLoader, 'loadScript'); - adapter.callBids(bidderRequest); + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'vertoz', + 'params': { + 'placementId': 'VZ-HB-B784382V6C6G3C' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should be called twice', () => { - sinon.assert.calledTwice(adLoader.loadScript); + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); - describe('Bid response', () => { - let vzBidRequest; - let bidderReponse = { - 'vzhPlacementId': 'VZ-HB-123', - 'bid': '0fac1b8a-6ba0-4641-bd57-2899b1bedeae_0', - 'adWidth': '300', - 'adHeight': '250', - 'cpm': '1.00000000000000', - 'ad': '<div></div>', - 'slotBidId': 'bidId1', - 'nurl': '<img></img>', - 'statusText': 'vertoz:success' - }; + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'vertoz', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; - beforeEach(() => { - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); + it('sends bid request to ENDPOINT via POST', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(BASE_URI); + expect(request.method).to.equal('POST'); }); + }); - describe('success', () => { - let firstBidReg; - let adSpaceId; - - beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); - $$PREBID_GLOBAL$$.vzResponse(bidderReponse); - firstBidReg = bidManager.addBidResponse.firstCall.args[1]; - adSpaceId = bidManager.addBidResponse.firstCall.args[0]; - }); + describe('interpretResponse', () => { + let response = { + 'vzhPlacementId': 'VZ-HB-B784382V6C6G3C', + 'bid': '76021e56-adaf-4114-b68d-ccacd1b3e551_1', + 'adWidth': '300', + 'adHeight': '250', + 'cpm': '0.16312590000000002', + 'ad': '<!-- Creative -->', + 'slotBidId': '44b3fcfd24aa93', + 'nurl': '<!-- Pixelurl -->', + 'statusText': 'Vertoz:Success' + }; - it('cpm to have property 1.000000', () => { - expect(firstBidReg).to.have.property('cpm', 1.00); - }); - it('adSpaceId should exist and be equal to placementCode', () => { - expect(adSpaceId).to.equal('foo'); - }); - it('should have property ad', () => { - expect(firstBidReg).to.have.property('ad'); - }); - it('should include the size to the bid object', () => { - expect(firstBidReg).to.have.property('width', '300'); - expect(firstBidReg).to.have.property('height', '250'); - }); + it('should get correct bid response', () => { + let expectedResponse = [ + { + 'requestId': '44b3fcfd24aa93', + 'cpm': 0.16312590000000002, + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'mediaType': 'banner', + 'currency': 'USD', + 'dealId': null, + 'creativeId': null, + 'ttl': 300, + 'ad': '<!-- Creative -->' + } + ]; + 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); }); - describe('failure', () => { - let secondBidReg; - let adSpaceId; - let bidderResponse = { - 'vzhPlacementId': 'VZ-HB-456', - 'slotBidId': 'bidId2', - 'statusText': 'vertoz:NO_BIDS' - } - - beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); - $$PREBID_GLOBAL$$.vzResponse(bidderResponse); - secondBidReg = bidManager.addBidResponse.firstCall.args[1]; - adSpaceId = bidManager.addBidResponse.firstCall.args[0]; - }); + it('handles nobid responses', () => { + let response = { + 'vzhPlacementId': 'VZ-HB-I617046VBGE3EH', + 'slotBidId': 'f00412ac86b79', + 'statusText': 'NO_BIDS' + }; + let bidderRequest; - it('should not have cpm property', () => { - expect(secondBidReg.cpm).to.be.undefined; - }); - it('adSpaceId should exist and be equal to placementCode', () => { - expect(adSpaceId).to.equal('bar'); - }); - it('should not have ad property', () => { - expect(secondBidReg.ad).to.be.undefined; - }); + let result = spec.interpretResponse({body: response}); + expect(result.length).to.equal(0); }); }); }); diff --git a/test/spec/modules/viBidAdapter_spec.js b/test/spec/modules/viBidAdapter_spec.js new file mode 100644 index 00000000000..e8b0fbcc4b2 --- /dev/null +++ b/test/spec/modules/viBidAdapter_spec.js @@ -0,0 +1,139 @@ +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', () => { + 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' + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when pubId not passed', () => { + bid.params.pubId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [{ + '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' + }]; + + const request = spec.buildRequests(bidRequests); + + it('POST bid request to vi', () => { + expect(request.method).to.equal('POST'); + }); + + it('check endpoint URL', () => { + expect(request.url).to.equal(ENDPOINT) + }); + }); + + describe('buildRequests can handle size in 1-dim array', () => { + let bidRequests = [{ + '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' + }]; + + const request = spec.buildRequests(bidRequests); + + it('POST bid request to vi', () => { + expect(request.method).to.equal('POST'); + }); + + it('check endpoint URL', () => { + expect(request.url).to.equal(ENDPOINT) + }); + }); + + describe('interpretResponse', () => { + let response = { + body: [{ + 'id': '29b891ad542377', + 'price': 0.1, + 'width': 320, + 'height': 480, + 'ad': '<!-- Real ad markup -->', + 'creativeId': 'dZsPGv' + }] + }; + + it('should get the correct bid response', () => { + let expectedResponse = [{ + 'requestId': '29b891ad542377', + 'cpm': 0.1, + 'width': 320, + 'height': 480, + 'creativeId': 'dZsPGv', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': decodeURIComponent(`<!-- Creative -->`), + 'ttl': 60000 + }]; + + let result = spec.interpretResponse(response); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('handles empty bid response', () => { + let response = { + body: [] + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js new file mode 100644 index 00000000000..d88e5a718ed --- /dev/null +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -0,0 +1,191 @@ +import {expect} from 'chai'; +import {spec as adapter, URL} from 'modules/vidazooBidAdapter'; +import * as utils from 'src/utils'; +const BID = { + 'bidId': '2d52001cabd527', + 'params': { + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a' +}; + +const SERVER_RESPONSE = { + body: { + 'ad': '<iframe>console.log("hello world")</iframe>', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +const SYNC_OPTIONS = { + 'pixelEnabled': true +}; + +describe('VidazooBidAdapter', () => { + describe('validtae spec', () => { + it('exists and is a function', () => { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', () => { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', () => { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', () => { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', () => { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + }); + + describe('validate bid requests', () => { + it('should require cId', () => { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', () => { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', () => { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', () => { + let sandbox; + before(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'getTopWindowUrl').returns('http://www.greatsite.com'); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build request for each size', () => { + const requests = adapter.buildRequests([BID]); + expect(requests).to.have.length(2); + expect(requests[0]).to.deep.equal({ + method: 'GET', + url: `${URL}/prebid/59db6b3b4ffaa70004f45cdc`, + data: { + width: '300', + height: '250', + url: 'http://www.greatsite.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + publisherId: '59ac17c192832d0011283fe3', + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + } + }); + expect(requests[1]).to.deep.equal({ + method: 'GET', + url: `${URL}/prebid/59db6b3b4ffaa70004f45cdc`, + data: { + width: '300', + height: '600', + url: 'http://www.greatsite.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + publisherId: '59ac17c192832d0011283fe3', + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + } + }); + }); + + after(() => { + sandbox.restore(); + }); + }); + + describe('interpret response', () => { + it('should return empty array when there is no response', () => { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', () => { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', () => { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted responses', () => { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '<iframe>console.log("hello world")</iframe>' + }); + }); + + it('should take default TTL', () => { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); +}); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js new file mode 100755 index 00000000000..20b056adf6d --- /dev/null +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -0,0 +1,356 @@ +import { expect } from 'chai'; +import { spec } from 'modules/visxBidAdapter'; +import { config } from 'src/config'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('VisxAdapter', function () { + 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', () => { + let bid = { + 'bidder': 'visx', + 'params': { + 'uid': '903536' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + 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', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903536' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', () => { + const request = spec.buildRequests([bidRequests[0]]); + 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', 'net'); + expect(payload).to.have.property('auids', '903535'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('cur', 'EUR'); + }); + + it('auids must not be duplicated', () => { + const request = spec.buildRequests(bidRequests); + 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', 'net'); + expect(payload).to.have.property('auids', '903535,903536'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('cur', 'EUR'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', () => { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests); + 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('r', '22edbae2733bf6'); + expect(payload).to.have.property('cur', 'EUR'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', () => { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests); + 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', 'net'); + expect(payload).to.have.property('auids', '903535,903536'); + 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', () => { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'currency.bidderCurrencyDefault.visx' ? 'JPY' : 'USD'); + const request = spec.buildRequests(bidRequests); + 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', 'net'); + expect(payload).to.have.property('auids', '903535,903536'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('cur', 'JPY'); + getConfigStub.restore(); + }); + it('should add currency from currency.adServerCurrency', () => { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'currency.bidderCurrencyDefault.visx' ? '' : 'USD'); + const request = spec.buildRequests(bidRequests); + 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', 'net'); + expect(payload).to.have.property('auids', '903535,903536'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('cur', 'USD'); + getConfigStub.restore(); + }); + }); + + describe('interpretResponse', () => { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '<div>test content 1</div>', 'auid': 903535, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '<div>test content 2</div>', 'auid': 903536, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 903536, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '<div>test content 4</div>', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', () => { + const bidRequests = [ + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + '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': 903535, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '<div>test content 1</div>', + 'currency': 'EUR', + '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', () => { + const bidRequests = [ + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903536' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + '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': 903535, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '<div>test content 1</div>', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 903535, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '<div>test content 1</div>', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 903536, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '<div>test content 2</div>', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0], responses[1]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should return right currency', () => { + const bidRequests = [ + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + } + ]; + const getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': 903535, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '<div>test content 1</div>', + 'currency': 'JPY', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + getConfigStub.restore(); + }); + + it('handles wrong and nobid responses', () => { + const bidRequests = [ + { + 'bidder': 'visx', + 'params': { + 'uid': '903536' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903538' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903539' + }, + '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/vubleAnalyticsAdapter_spec.js b/test/spec/modules/vubleAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..896f6e4ee87 --- /dev/null +++ b/test/spec/modules/vubleAnalyticsAdapter_spec.js @@ -0,0 +1,122 @@ +import vubleAnalytics from 'modules/vubleAnalyticsAdapter'; +import { expect } from 'chai'; +let events = require('src/events'); +let adaptermanager = require('src/adaptermanager'); +let constants = require('src/constants.json'); + +describe('Vuble Prebid Analytic', function () { + let xhr; + before(() => { + xhr = sinon.useFakeXMLHttpRequest(); + }); + after(() => { + vubleAnalytics.disableAnalytics(); + xhr.restore(); + }); + + describe('enableAnalytics', function () { + beforeEach(() => { + sinon.spy(vubleAnalytics, 'track'); + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(() => { + vubleAnalytics.track.restore(); + events.getEvents.restore(); + }); + it('should catch all events', function () { + adaptermanager.registerAnalyticsAdapter({ + code: 'vuble', + adapter: vubleAnalytics + }); + + adaptermanager.enableAnalytics({ + provider: 'vuble', + options: { + pubId: 18, + env: 'net' + } + }); + + let auction_id = 'test'; + + // Step 1: Auction init + events.emit(constants.EVENTS.AUCTION_INIT, { + auctionId: auction_id, + timestamp: 1496510254313, + }); + + // Step 2: Bid request + events.emit(constants.EVENTS.BID_REQUESTED, { + auctionId: auction_id, + auctionStart: 1509369418387, + timeout: 3000, + bids: [ + { + bidder: 'vuble', + params: { + env: 'net', + pubId: '3', + zoneId: '12345', + floorPrice: 5.50 // optional + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bidId: 'abdc' + }, + { + bidder: 'vuble', + params: { + env: 'com', + pubId: '8', + zoneId: '2468', + referrer: 'http://www.vuble.fr/' + }, + sizes: '640x360', + mediaTypes: { + video: { + context: 'outstream' + } + }, + bidId: 'efgh', + }, + ], + }); + + // Step 3: Bid response + events.emit(constants.EVENTS.BID_RESPONSE, { + width: '640', + height: '360', + pub_id: '3', + dealId: 'aDealId', + zone_id: '12345', + context: 'instream', + floor_price: 5.5, + url: 'http://www.vuble.tv/', + env: 'net', + bid_id: 'abdc' + }); + + // Step 4: Bid won + events.emit(constants.EVENTS.BID_WON, { + adId: 'adIdTestWin', + ad: 'adContentTestWin', + auctionId: auction_id, + width: 640, + height: 360 + }); + + // Step 4: Auction end + events.emit(constants.EVENTS.AUCTION_END, { + auctionId: auction_id + }); + + // Step 5: Check if the number of call is good (5) + sinon.assert.callCount(vubleAnalytics.track, 5); + }); + }); +}); diff --git a/test/spec/modules/vubleBidAdapter_spec.js b/test/spec/modules/vubleBidAdapter_spec.js new file mode 100644 index 00000000000..61f00ef2c3d --- /dev/null +++ b/test/spec/modules/vubleBidAdapter_spec.js @@ -0,0 +1,271 @@ +// import or require modules necessary for the test, e.g.: + +import {expect} from 'chai'; +import {spec as adapter} from 'modules/vubleBidAdapter'; +import * as utils from 'src/utils'; + +describe('VubleAdapter', () => { + describe('Check methods existance', () => { + it('exists and is a function', () => { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', () => { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', () => { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', () => { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + }); + + describe('Check method isBidRequestValid return', () => { + let bid = { + bidder: 'vuble', + params: { + env: 'net', + pubId: '3', + zoneId: '12345', + floorPrice: 5.00 // optional + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + }; + + it('should be true', () => { + expect(adapter.isBidRequestValid(bid)).to.be.true; + }); + + it('should be false because the sizes are missing or in the wrong format', () => { + let wrongBid = utils.deepClone(bid); + wrongBid.sizes = '640360'; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + + wrongBid = utils.deepClone(bid); + delete wrongBid.sizes; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + }); + + it('should be false because the mediaType is missing or wrong', () => { + let wrongBid = utils.deepClone(bid); + wrongBid.mediaTypes = {}; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + + wrongBid = utils.deepClone(bid); + delete wrongBid.mediaTypes; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + }); + + it('should be false because the env is missing or wrong', () => { + let wrongBid = utils.deepClone(bid); + wrongBid.params.env = 'us'; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + + wrongBid = utils.deepClone(bid); + delete wrongBid.params.env; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + }); + + it('should be false because params.pubId is missing', () => { + let wrongBid = utils.deepClone(bid); + delete wrongBid.params.pubId; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + }); + + it('should be false because params.zoneId is missing', () => { + let wrongBid = utils.deepClone(bid); + delete wrongBid.params.zoneId; + expect(adapter.isBidRequestValid(wrongBid)).to.be.false; + }); + }); + + describe('Check buildRequests method', () => { + let sandbox; + before(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'getTopWindowUrl').returns('http://www.vuble.tv/'); + }); + + // Bids to be formatted + let bid1 = { + bidder: 'vuble', + params: { + env: 'net', + pubId: '3', + zoneId: '12345', + floorPrice: 5.50 // optional + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bidId: 'abdc' + }; + let bid2 = { + bidder: 'vuble', + params: { + env: 'com', + pubId: '8', + zoneId: '2468', + referrer: 'http://www.vuble.fr/' + }, + sizes: '640x360', + mediaTypes: { + video: { + context: 'outstream' + } + }, + bidId: 'efgh' + }; + + // Formatted requets + let request1 = { + method: 'POST', + url: '//player.mediabong.net/prebid/request', + data: { + width: '640', + height: '360', + pub_id: '3', + zone_id: '12345', + context: 'instream', + floor_price: 5.5, + url: 'http://www.vuble.tv/', + env: 'net', + bid_id: 'abdc' + } + }; + let request2 = { + method: 'POST', + url: '//player.mediabong.com/prebid/request', + data: { + width: '640', + height: '360', + pub_id: '8', + zone_id: '2468', + context: 'outstream', + floor_price: 0, + url: 'http://www.vuble.fr/', + env: 'com', + bid_id: 'efgh' + } + }; + + it('must return the right formatted requests', () => { + let rs = adapter.buildRequests([bid1, bid2]); + expect(adapter.buildRequests([bid1, bid2])).to.deep.equal([request1, request2]); + }); + + after(() => { + sandbox.restore(); + }); + }); + + describe('Check interpretResponse method return', () => { + // 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' + } + }; + // bid Request + let bid = { + data: { + context: 'instream', + env: 'net', + width: '640', + height: '360', + pub_id: '3', + zone_id: '12345', + bid_id: 'abdc', + floor_price: 5.50 // optional + }, + method: 'POST', + url: '//player.mediabong.net/prebid/request' + }; + // Formatted reponse + let result = { + requestId: 'abdc', + cpm: 5.00, + width: '640', + height: '360', + ttl: 60, + creativeId: '2468', + dealId: 'MDB-TEST-1357', + netRevenue: true, + currency: 'USD', + vastUrl: 'https//player.mediabong.net/prebid/ad/a1b2c3d4', + mediaType: 'video' + }; + + it('should equal to the expected formatted result', () => { + expect(adapter.interpretResponse(response, bid)).to.deep.equal([result]); + }); + + it('should be empty because the status is missing or wrong', () => { + let wrongResponse = utils.deepClone(response); + wrongResponse.body.status = 'ko'; + expect(adapter.interpretResponse(wrongResponse, bid)).to.be.empty; + + wrongResponse = utils.deepClone(response); + delete wrongResponse.body.status; + expect(adapter.interpretResponse(wrongResponse, bid)).to.be.empty; + }); + + it('should be empty because the body is missing or wrong', () => { + let wrongResponse = utils.deepClone(response); + wrongResponse.body = [1, 2, 3]; + expect(adapter.interpretResponse(wrongResponse, bid)).to.be.empty; + + wrongResponse = utils.deepClone(response); + delete wrongResponse.body; + expect(adapter.interpretResponse(wrongResponse, bid)).to.be.empty; + }); + }); + + describe('Check getUserSyncs method return', () => { + // Sync options + let syncOptions = { + iframeEnabled: false + }; + // Server's response + let response = { + body: { + status: 'ok', + cpm: 5.00, + creativeId: '2468', + url: 'https//player.mediabong.net/prebid/ad/a1b2c3d4' + } + }; + // Formatted reponse + let result = { + type: 'iframe', + url: 'http://player.mediabong.net/csifr?1234' + }; + + it('should return an empty array', () => { + expect(adapter.getUserSyncs({}, [])).to.be.empty; + expect(adapter.getUserSyncs({}, [])).to.be.empty; + expect(adapter.getUserSyncs(syncOptions, [response])).to.be.empty; + expect(adapter.getUserSyncs(syncOptions, [response])).to.be.empty; + syncOptions.iframeEnabled = true; + expect(adapter.getUserSyncs(syncOptions, [response])).to.be.empty; + expect(adapter.getUserSyncs(syncOptions, [response])).to.be.empty; + }); + + it('should be equal to the expected result', () => { + response.body.iframeSync = 'http://player.mediabong.net/csifr?1234'; + expect(adapter.getUserSyncs(syncOptions, [response])).to.deep.equal([result]); + }) + }); +}); diff --git a/test/spec/modules/wideorbitBidAdapter_spec.js b/test/spec/modules/wideorbitBidAdapter_spec.js deleted file mode 100644 index 9ace04883e6..00000000000 --- a/test/spec/modules/wideorbitBidAdapter_spec.js +++ /dev/null @@ -1,497 +0,0 @@ -describe('wideorbit adapter tests', function () { - var expect = require('chai').expect; - var urlParse = require('url-parse'); - - // FYI: querystringify will perform encoding/decoding - var querystringify = require('querystringify'); - - var adapter = require('modules/wideorbitBidAdapter'); - var adLoader = require('src/adloader'); - var bidmanager = require('src/bidmanager'); - - describe('creation of bid url', function () { - let stubLoadScript; - - beforeEach(function () { - stubLoadScript = sinon.stub(adLoader, 'loadScript'); - }); - - afterEach(function () { - stubLoadScript.restore(); - }); - - it('should be called only once', function () { - var params = { - bidderCode: 'wideorbit', - bids: [ - { - bidder: 'wideorbit', - params: { - pbId: 1, - pId: 101 - }, - placementCode: 'div-gpt-ad-12345-1' - }, - { - bidder: 'wideorbit', - params: { - pbId: 1, - site: 'Site 1', - page: 'Page 1', - width: 100, - height: 200, - subPublisher: 'Sub Publisher 1' - }, - placementCode: 'div-gpt-ad-12345-2' - } - ] - }; - - adapter().callBids(params); - - sinon.assert.calledOnce(stubLoadScript); - }); - - it('should fix parameters name', function () { - var params = { - bidderCode: 'wideorbit', - bids: [ - { - bidder: 'wideorbit', - params: { - PBiD: 1, - PID: 101, - ReferRer: 'http://www.foo.com?param1=param1¶m2=param2' - }, - placementCode: 'div-gpt-ad-12345-1' - }, - { - bidder: 'wideorbit', - params: { - pbid: 1, - SiTe: 'Site 1', - Page: 'Page 1', - widTH: 100, - HEIGHT: 200, - SUBPublisher: 'Sub Publisher 1' - }, - placementCode: 'div-gpt-ad-12345-2' - } - ] - }; - - adapter().callBids(params); - - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledWith(stubLoadScript, bidUrl); - - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') - expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') - expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); - expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); - expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); - expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); - expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); - expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.$$PREBID_GLOBAL$$.handleWideOrbitCallback'); - expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); - expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); - expect(parsedBidUrlQueryString).to.have.property('url').and.to.equal('http://www.foo.com?param1=param1¶m2=param2'); - - expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); - expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal(''); - - expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); - expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal(''); - - expect(parsedBidUrlQueryString).to.have.property('pId0').and.to.equal('101'); - expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); - - expect(parsedBidUrlQueryString).to.have.property('wsName1').and.to.equal('Site 1'); - expect(parsedBidUrlQueryString).to.have.property('wName1').and.to.equal('Page 1'); - expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('1'); - expect(parsedBidUrlQueryString).to.have.property('bfDim1').and.to.equal('100x200'); - expect(parsedBidUrlQueryString).to.have.property('subp1').and.to.equal('Sub Publisher 1'); - }); - - describe('placement by name', function () { - it('should be called with specific parameters for two bids', function () { - var params = { - bidderCode: 'wideorbit', - bids: [ - { - bidder: 'wideorbit', - params: { - pbId: 1, - site: 'Site 1', - page: 'Page 1', - width: 100, - height: 200, - subPublisher: 'Sub Publisher 1', - atf: true - }, - placementCode: 'div-gpt-ad-12345-1' - }, - { - bidder: 'wideorbit', - params: { - pbId: 1, - site: 'Site 2', - page: 'Page 2', - width: 200, - height: 300, - rank: 123, - ecpm: 1.8 - }, - placementCode: 'div-gpt-ad-12345-2' - } - ] - }; - - adapter().callBids(params); - - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledWith(stubLoadScript, bidUrl); - - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') - expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') - expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); - expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); - expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); - expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); - expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); - expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.$$PREBID_GLOBAL$$.handleWideOrbitCallback'); - expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); - expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); - expect(parsedBidUrlQueryString).to.have.property('url').and.to.be.empty; - - expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); - expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('1001'); - expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal(''); - - expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); - expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal('1.8'); - - expect(parsedBidUrlQueryString).to.have.property('wsName0').and.to.equal('Site 1'); - expect(parsedBidUrlQueryString).to.have.property('wName0').and.to.equal('Page 1'); - expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('bfDim0').and.to.equal('100x200'); - expect(parsedBidUrlQueryString).to.have.property('subp0').and.to.equal('Sub Publisher 1'); - - expect(parsedBidUrlQueryString).to.have.property('wsName1').and.to.equal('Site 2'); - expect(parsedBidUrlQueryString).to.have.property('wName1').and.to.equal('Page 2'); - expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('123'); - expect(parsedBidUrlQueryString).to.have.property('bfDim1').and.to.equal('200x300'); - expect(parsedBidUrlQueryString).to.have.property('subp1').and.to.equal(''); - }); - }); - - describe('placement by id', function () { - it('should be called with specific parameters for two bids', function () { - var params = { - bidderCode: 'wideorbit', - bids: [ - { - bidder: 'wideorbit', - params: { - pbId: 1, - pId: 101, - atf: true, - ecpm: 0.8 - }, - placementCode: 'div-gpt-ad-12345-1' - }, - { - bidder: 'wideorbit', - params: { - pbId: 1, - pId: 102, - rank: 123 - }, - placementCode: 'div-gpt-ad-12345-2' - } - ] - }; - - adapter().callBids(params); - - var bidUrl = stubLoadScript.getCall(0).args[0]; - - sinon.assert.calledWith(stubLoadScript, bidUrl); - - var parsedBidUrl = urlParse(bidUrl); - var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); - - expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') - expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') - expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); - expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); - expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); - expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); - expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); - expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.$$PREBID_GLOBAL$$.handleWideOrbitCallback'); - expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); - expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); - expect(parsedBidUrlQueryString).to.have.property('url').and.to.be.empty; - - expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); - expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('1001'); - expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal('0.8'); - - expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); - expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); - expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal(''); - - expect(parsedBidUrlQueryString).to.have.property('pId0').and.to.equal('101'); - expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); - - expect(parsedBidUrlQueryString).to.have.property('pId1').and.to.equal('102'); - expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('123'); - }); - }); - }); - - // describe('handling of the callback response', function () { - // - // var placements = [ - // { - // ExtPlacementId: 'div-gpt-ad-12345-1', - // Type: 'DirectHTML', - // Bid: 1.3, - // Width: 50, - // Height: 100, - // Source: '<div data-id="div-gpt-ad-12345-1">The AD 1 itself...</div>', - // TrackingCodes: [ - // 'https://www.admeta.com/1.gif' - // ] - // }, - // { - // ExtPlacementId: 'div-gpt-ad-12345-2', - // Type: 'DirectHTML', - // Bid: 1.5, - // Width: 100, - // Height: 200, - // Source: '<div data-id="div-gpt-ad-12345-2">The AD 2 itself...</div>', - // TrackingCodes: [ - // 'http://www.admeta.com/2a.gif', - // '<img src="http://www.admeta.com/2b.gif"></img>' - // ] - // }, - // { - // ExtPlacementId: 'div-gpt-ad-12345-3', - // Type: 'Other', - // Bid: 1.7, - // Width: 150, - // Height: 250, - // Source: '<div data-id="div-gpt-ad-12345-3">The AD 3 itself...</div>', - // TrackingCodes: [ - // 'http://www.admeta.com/3.gif' - // ] - // } - // ]; - // - // it('callback function should exist', function () { - // expect($$PREBID_GLOBAL$$.handleWideOrbitCallback).to.exist.and.to.be.a('function'); - // }); - // - // it('bidmanager.addBidResponse should be called thrice with correct arguments', function () { - // - // var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - // - // var params = { - // bidderCode: 'wideorbit', - // bids: [ - // { - // bidder: 'wideorbit', - // params: { - // pbId: 1, - // pId: 101 - // }, - // placementCode: 'div-gpt-ad-12345-1' - // }, - // { - // bidder: 'wideorbit', - // params: { - // pbId: 1, - // site: 'Site 1', - // page: 'Page 1', - // width: 100, - // height: 200, - // subPublisher: 'Sub Publisher 1' - // }, - // placementCode: 'div-gpt-ad-12345-2' - // }, - // { - // bidder: 'wideorbit', - // params: { - // pbId: 1, - // pId: 102 - // }, - // placementCode: 'div-gpt-ad-12345-3' - // }, - // ] - // }; - // - // var response = { - // UserMatchings: [ - // { - // Type: 'redirect', - // Url: 'http%3A%2F%2Fwww.admeta.com%2F1.gif' - // } - // ], - // Placements: placements - // }; - // - // adapter().callBids(params); - // $$PREBID_GLOBAL$$.handleWideOrbitCallback(response); - // - // var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; - // var bidObject1 = stubAddBidResponse.getCall(0).args[1]; - // var bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0]; - // var bidObject2 = stubAddBidResponse.getCall(1).args[1]; - // var bidPlacementCode3 = stubAddBidResponse.getCall(2).args[0]; - // var bidObject3 = stubAddBidResponse.getCall(2).args[1]; - // - // expect(bidPlacementCode1).to.equal('div-gpt-ad-12345-1'); - // expect(bidObject1.cpm).to.equal(1.3); - // expect(bidObject1.ad).to.equal('<img src="https://www.admeta.com/1.gif" width="0" height="0" style="position:absolute"></img><div data-id="div-gpt-ad-12345-1">The AD 1 itself...</div>'); - // expect(bidObject1.width).to.equal(50); - // expect(bidObject1.height).to.equal(100); - // expect(bidObject1.getStatusCode()).to.equal(1); - // expect(bidObject1.bidderCode).to.equal('wideorbit'); - // - // expect(bidPlacementCode2).to.equal('div-gpt-ad-12345-2'); - // expect(bidObject2.cpm).to.equal(1.50); - // expect(bidObject2.ad).to.equal('<img src="http://www.admeta.com/2b.gif"></img><img src="http://www.admeta.com/2a.gif" width="0" height="0" style="position:absolute"></img><div data-id="div-gpt-ad-12345-2">The AD 2 itself...</div>'); - // expect(bidObject2.width).to.equal(100); - // expect(bidObject2.height).to.equal(200); - // expect(bidObject2.getStatusCode()).to.equal(1); - // expect(bidObject2.bidderCode).to.equal('wideorbit'); - // - // expect(bidPlacementCode3).to.equal('div-gpt-ad-12345-3'); - // expect(bidObject3.getStatusCode()).to.equal(2); - // expect(bidObject3.bidderCode).to.equal('wideorbit'); - // - // sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode1, bidObject1); - // sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode2, bidObject2); - // sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode3, bidObject3); - // - // sinon.assert.calledThrice(stubAddBidResponse); - // - // stubAddBidResponse.restore(); - // - // }); - // - // it('should append an image to the head when type is set to redirect', function () { - // - // var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - // - // var response = { - // UserMatchings: [ - // { - // Type: 'redirect', - // Url: 'http%3A%2F%2Fwww.admeta.com%2F1.gif' - // } - // ], - // Placements: placements - // }; - // - // $$PREBID_GLOBAL$$.handleWideOrbitCallback(response); - // - // var imgElement = document.querySelectorAll("head img")[0]; - // - // expect(imgElement).to.exist; - // expect(imgElement.src).to.equal('http://www.admeta.com/1.gif'); - // - // stubAddBidResponse.restore(); - // }); - // - // it('should append an iframe to the head when type is set to iframe', function () { - // - // var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - // - // var response = { - // UserMatchings: [ - // { - // Type: 'iframe', - // Url: 'http%3A%2F%2Fwww.admeta.com%2F1.ashx' - // } - // ], - // Placements: placements - // }; - // - // $$PREBID_GLOBAL$$.handleWideOrbitCallback(response); - // - // var iframeElement = document.querySelectorAll("head iframe")[0]; - // - // expect(iframeElement).to.exist; - // expect(iframeElement.src).to.equal('http://www.admeta.com/1.ashx'); - // - // stubAddBidResponse.restore(); - // - // }); - // - // it('should append an script to the head when type is set to js', function () { - // - // var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - // - // var response = { - // UserMatchings: [ - // { - // Type: 'js', - // Url: 'http%3A%2F%2Fwww.admeta.com%2F1.js' - // } - // ], - // Placements: placements - // }; - // - // $$PREBID_GLOBAL$$.handleWideOrbitCallback(response); - // - // var scriptElement = document.querySelectorAll("head script")[0]; - // - // expect(scriptElement).to.exist; - // expect(scriptElement.src).to.equal('http://www.admeta.com/1.js'); - // - // stubAddBidResponse.restore(); - // }); - // - // it('should do nothing when type is set to unrecognized type', function () { - // - // var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); - // - // var response = { - // UserMatchings: [ - // { - // Type: 'unrecognized', - // Url: 'http%3A%2F%2Fwww.admeta.com%2F1.js' - // } - // ], - // Placements: placements - // }; - // - // $$PREBID_GLOBAL$$.handleWideOrbitCallback(response); - // - // stubAddBidResponse.restore(); - // }); - // - // }); -}); diff --git a/test/spec/modules/widespaceBidAdapter_spec.js b/test/spec/modules/widespaceBidAdapter_spec.js index 75486baa25b..e1c922c2b9c 100644 --- a/test/spec/modules/widespaceBidAdapter_spec.js +++ b/test/spec/modules/widespaceBidAdapter_spec.js @@ -1,226 +1,217 @@ import { expect } from 'chai'; -import adLoader from '../../../src/adloader'; -import bidManager from '../../../src/bidmanager'; -import Adapter from '../../../modules/widespaceBidAdapter'; - -const ENDPOINT = '//engine.widespace.com/map/engine/hb/dynamic'; - -const TEST = { - BIDDER_CODE: 'widespace', - CPM: 2.0, - PLACEMENT_CODE: 'aPlacementCode', - SID: 'f666bfaf-69cf-4ed9-9262-08247bb274e4', - CUR: 'EUR' -}; - -const BID_REQUEST = { - 'bidderCode': TEST.BIDDER_CODE, - 'requestId': 'e155185b-3eac-4f3c-8182-cdb57a69df3c', - 'bidderRequestId': '38993e482321e7', - 'bids': [ - { - 'bidder': TEST.BIDDER_CODE, - 'params': { - 'sid': TEST.SID, - 'cur': TEST.CUR - }, - 'placementCode': TEST.PLACEMENT_CODE, - 'sizes': [ - [320, 320], - [320, 250] - ], - 'bidId': '45c7f5afb996c1', - 'bidderRequestId': '7101db09af0db3', - 'requestId': 'e155185b-3eac-4f3c-8182-cdb57a69df3d' - } - ], - 'start': 1479664180396, - 'timeout': 5000 -}; - -let bidRequestWithDemoData = BID_REQUEST; - -const BID_RESPONSE = [{ - 'status': 'ok', - 'reqId': '140590112507', - 'adId': 13963, - 'width': 320, - 'height': 320, - 'cpm': 2.0, - 'currency': 'EUR', - 'code': '<p>This is a banner</p>', - 'callbackUid': '45c7f5afb996c1', - 'callback': 'pbjs.widespaceHandleCB' -}]; - -const BID_NOAD_RESPONSE = [{ - 'status': 'noad', - 'reqId': '143509454349', - 'adId': 22, - 'width': 1, - 'height': 1, - 'cpm': 0.0, - 'currency': 'EUR', - 'code': '', - 'callbackUid': '45c7f5afb996c1', - 'callback': 'pbjs.widespaceHandleCB' -}] - -describe('WidespaceAdapter', () => { - let adapter; - let sandbox; - - beforeEach(() => { - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('callBids', () => { - it('should exists and be a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); +import { spec } from 'modules/widespaceBidAdapter'; +import includes from 'core-js/library/fn/array/includes'; + +describe('+widespaceAdatperTest', () => { + // Dummy bid request + const bidRequest = [{ + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': 'bf1e57ee-fff2-4304-8143-91aaf423a948', + 'bidId': '4045696e2278cd', + 'bidder': 'widespace', + 'params': { + sid: '7b6589bf-95c8-4656-90b9-af9737bb9ad3', + currency: 'EUR', + demo: { + gender: 'M', + country: 'Sweden', + region: 'Stockholm', + postal: '15115', + city: 'Stockholm', + yob: '1984' + } + }, + 'bidderRequestId': '37a5f053efef34', + 'sizes': [[320, 320], [300, 250], [300, 300]], + 'transactionId': '4f68b713-04ba-4d7f-8df9-643bcdab5efb' + }, { + 'adUnitCode': 'div-gpt-ad-1460505748561-1', + 'auctionId': 'bf1e57ee-fff2-4304-8143-91aaf423a944', + 'bidId': '4045696e2278ab', + 'bidder': 'widespace', + 'params': { + sid: '7b6589bf-95c8-4656-90b9-af9737bb9ad4', + demo: { + gender: 'M', + country: 'Sweden', + region: 'Stockholm', + postal: '15115', + city: 'Stockholm', + yob: '1984' + } + }, + 'bidderRequestId': '37a5f053efef34', + 'sizes': [[300, 300]], + 'transactionId': '4f68b713-04ba-4d7f-8df9-643bcdab5efv' + }]; + + // Dummy bid response with ad code + const bidResponse = { + body: [{ + 'adId': '12345', + 'bidId': '67890', + 'code': '<div></div>', + 'cpm': 6.6, + 'currency': 'EUR', + 'height': 300, + 'netRev': true, + 'reqId': '224804081406', + 'status': 'ad', + 'ttl': 30, + 'width': 300, + 'syncPixels': ['https://url1.com/url', 'https://url2.com/url'] + }], + headers: {} + }; + + // Dummy bid response of noad + const bidResponseNoAd = { + body: [{ + 'status': 'noad', + }], + headers: {} + }; + + // Appending a div with id of adUnitCode so we can calculate vol + const div1 = document.createElement('div'); + div1.id = bidRequest[0].adUnitCode; + document.body.appendChild(div1); + const div2 = document.createElement('div'); + div2.id = bidRequest[0].adUnitCode; + document.body.appendChild(div2); + + // Adding custom data cookie se we can test cookie is readable + const theDate = new Date(); + const expDate = new Date(theDate.setMonth(theDate.getMonth() + 1)).toGMTString(); + window.document.cookie = `wsCustomData1={id: test};path=/;expires=${expDate};`; + const PERF_DATA = JSON.stringify({perf_status: 'OK', perf_reqid: '226920425154', perf_ms: '747'}); + window.document.cookie = `wsPerfData123=${PERF_DATA};path=/;expires=${expDate};`; + + // Connect dummy data test + const CONNECTION = navigator.connection || navigator.webkitConnection; + if (CONNECTION && CONNECTION.type && CONNECTION.downlinkMax) { + navigator.connection.downlinkMax = 80; + navigator.connection.type = 'wifi'; + } + + describe('+bidRequestValidity', () => { + it('bidRequest with sid and currency params', () => { + expect(spec.isBidRequestValid({ + bidder: 'widespace', + params: { + sid: '7b6589bf-95c8-4656-90b9-af9737bb9ad3', + currency: 'EUR' + } + })).to.equal(true); }); - describe('with valid request parameters', () => { - beforeEach(() => { - sandbox.stub(adLoader, 'loadScript'); - adapter.callBids(BID_REQUEST); - }); - - it('should call the endpoint once per valid bid', () => { - sinon.assert.callCount(adLoader.loadScript, 1); - }); - - it('should include required request parameters', () => { - const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]); - endpointRequest.to.include('sid'); - endpointRequest.to.include('hb'); - endpointRequest.to.include('hb.ver'); - endpointRequest.to.include('hb.callbackUid'); - endpointRequest.to.include('hb.callback'); - endpointRequest.to.include('hb.sizes'); - endpointRequest.to.include('hb.name'); - }); + it('-bidRequest with missing sid', () => { + expect(spec.isBidRequestValid({ + bidder: 'widespace', + params: { + currency: 'EUR' + } + })).to.equal(false); }); - describe('with valid request parameters (demo data)', () => { - beforeEach(() => { - sandbox.stub(adLoader, 'loadScript'); - bidRequestWithDemoData = BID_REQUEST; - }); - - it('should include required request parameters', () => { - bidRequestWithDemoData.bids[0].params.demo = { - gender: 'F', - country: 'UK', - region: 'Greater London', - postal: 'W1U 8EW', - city: 'London', - yob: 1981 - }; - - adapter.callBids(bidRequestWithDemoData); - - const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]); - endpointRequest.to.include('hb.demo.gender'); - endpointRequest.to.include('hb.demo.country'); - endpointRequest.to.include('hb.demo.region'); - endpointRequest.to.include('hb.demo.postal'); - endpointRequest.to.include('hb.demo.city'); - endpointRequest.to.include('hb.demo.yob'); - }); - - it('should not include "hb.demo.gender" as a request parameter, if it hasn\'t been specified', () => { - bidRequestWithDemoData.bids[0].params.demo = { - country: 'UK', - region: 'Greater London', - postal: 'W1U 8EW', - city: 'London', - yob: 1981 - }; - - adapter.callBids(bidRequestWithDemoData); - - const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]); - endpointRequest.to.not.include('hb.demo.gender'); - }); + it('-bidRequest with missing currency', () => { + expect(spec.isBidRequestValid({ + bidder: 'widespace', + params: { + sid: '7b6589bf-95c8-4656-90b9-af9737bb9ad3' + } + })).to.equal(true); }); + }); - describe('with unvalid request parameters', () => { - beforeEach(() => { - sandbox.stub(adLoader, 'loadScript'); - }); + describe('+bidRequest', () => { + const request = spec.buildRequests(bidRequest); + const UrlRegExp = /^((ftp|http|https):)?\/\/[^ "]+$/; - it('should not call the endpoint with if there is no request parameters', () => { - adapter.callBids({}); - sinon.assert.callCount(adLoader.loadScript, 0); - }); + it('-bidRequest method is POST', () => { + expect(request[0].method).to.equal('POST'); }); - }); - describe('widespaceHandleCB', () => { - it('should exist and be a function', () => { - expect($$PREBID_GLOBAL$$.widespaceHandleCB).to.exist.and.to.be.a('function'); + it('-bidRequest url is valid', () => { + expect(UrlRegExp.test(request[0].url)).to.equal(true); }); - }); - - describe('respond with a successful bid', () => { - let successfulBid, - placementCode; - beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); - sandbox.stub(adLoader, 'loadScript'); - - adapter.callBids(BID_REQUEST); - $$PREBID_GLOBAL$$._bidsRequested.push(BID_REQUEST); - $$PREBID_GLOBAL$$.widespaceHandleCB(BID_RESPONSE); + it('-bidRequest data exist', () => { + expect(request[0].data).to.exists; + }); - successfulBid = bidManager.addBidResponse.firstCall.args[1]; - placementCode = bidManager.addBidResponse.firstCall.args[0]; + it('-bidRequest data is form data', () => { + expect(typeof request[0].data).to.equal('string'); }); - it('should add one bid', () => { - sinon.assert.calledOnce(bidManager.addBidResponse); + it('-bidRequest options have header type', () => { + expect(request[0].options.contentType).to.exists; }); - it('should use the CPM returned by the server', () => { - expect(successfulBid).to.have.property('cpm', TEST.CPM); + it('-cookie test for wsCustomData ', () => { + expect(request[0].data.indexOf('hb.cd') > -1).to.equal(true); }); + }); - it('should have an OK statusCode', () => { - expect(successfulBid.getStatusCode()).to.eql(1); + describe('+interpretResponse', () => { + it('-required params available in response', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + let requiredKeys = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'creativeId', + 'currency', + 'netRevenue', + 'ttl', + 'referrer', + 'ad' + ]; + const resultKeys = Object.keys(result[0]); + requiredKeys.forEach((key) => { + expect(includes(resultKeys, key)).to.equal(true); + }); + + // Each value except referrer should not be empty|null|undefined + result.forEach((res) => { + Object.keys(res).forEach((resKey) => { + if (resKey !== 'referrer') { + expect(res[resKey]).to.not.be.null; + expect(res[resKey]).to.not.be.undefined; + expect(res[resKey]).to.not.equal(''); + } + }); + }); }); - it('should have a valid size', () => { - const bidSize = [successfulBid.width, successfulBid.height] - expect(bidSize).to.eql(BID_REQUEST.bids[0].sizes[0]); + it('-empty result if noad responded', () => { + const noAdResult = spec.interpretResponse(bidResponseNoAd, bidRequest); + expect(noAdResult.length).to.equal(0); }); - it('should recive right placementCode', () => { - expect(placementCode).to.eql(TEST.PLACEMENT_CODE); + it('-empty response should not breake anything in adapter', () => { + const noResponse = spec.interpretResponse({}, bidRequest); + expect(noResponse.length).to.equal(0); }); }); - describe('respond with a no-ad', () => { - let noadBid; + describe('+getUserSyncs', () => { + it('-always return an array', () => { + const userSync_test1 = spec.getUserSyncs({}, [bidResponse]); + expect(Array.isArray(userSync_test1)).to.equal(true); - beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); - sandbox.stub(adLoader, 'loadScript'); + const userSync_test2 = spec.getUserSyncs({}, [bidResponseNoAd]); + expect(Array.isArray(userSync_test2)).to.equal(true); - adapter.callBids(BID_REQUEST); - $$PREBID_GLOBAL$$._bidsRequested.push(BID_REQUEST); - $$PREBID_GLOBAL$$.widespaceHandleCB(BID_NOAD_RESPONSE); + const userSync_test3 = spec.getUserSyncs({}, [bidResponse, bidResponseNoAd]); + expect(Array.isArray(userSync_test3)).to.equal(true); - noadBid = bidManager.addBidResponse.firstCall.args[1]; - }); + const userSync_test4 = spec.getUserSyncs(); + expect(Array.isArray(userSync_test4)).to.equal(true); - it('should have an error statusCode', () => { - expect(noadBid.getStatusCode()).to.eql(2); + const userSync_test5 = spec.getUserSyncs({}, []); + expect(Array.isArray(userSync_test5)).to.equal(true); }); }); }); diff --git a/test/spec/modules/xendizBidAdapter_spec.js b/test/spec/modules/xendizBidAdapter_spec.js new file mode 100644 index 00000000000..66b9dc62b88 --- /dev/null +++ b/test/spec/modules/xendizBidAdapter_spec.js @@ -0,0 +1,119 @@ +import { expect } from 'chai'; +import { spec } from 'modules/xendizBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const VALID_ENDPOINT = '//prebid.xendiz.com/request'; +const bidRequest = { + bidder: 'xendiz', + adUnitCode: 'test-div', + sizes: [[300, 250], [300, 600]], + params: { + pid: '550e8400-e29b-41d4-a716-446655440000' + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', +}; + +const bidResponse = { + body: { + id: '1d1a030790a475', + bids: [{ + id: '30b31c1838de1e', + price: 3, + cur: 'USD', + h: 250, + w: 300, + crid: 'test', + dealid: '1', + exp: 900, + adm: 'tag' + }] + } +}; + +const noBidResponse = { body: { id: '1d1a030790a475', bids: [] } }; + +describe('xendizBidAdapter', () => { + 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 false', () => { + let bid = Object.assign({}, bidRequest); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true', () => { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + }); + + describe('buildRequests', () => { + it('should format valid url', () => { + const request = spec.buildRequests([bidRequest]); + expect(request.url).to.equal(VALID_ENDPOINT); + }); + + it('should format valid url', () => { + const request = spec.buildRequests([bidRequest]); + expect(request.url).to.equal(VALID_ENDPOINT); + }); + + it('should format valid request body', () => { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.id).to.exist; + expect(payload.items).to.exist; + expect(payload.device).to.exist; + }); + + it('should attach valid device info', () => { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.device).to.deep.equal([ + navigator.language || '', + window.screen.width, + window.screen.height + ]); + }); + + it('should transform sizes', () => { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + const item = payload.items[0]; + expect(item[item.length - 1]).to.deep.equal(['300x250', '300x600']); + }); + }); + + describe('interpretResponse', () => { + it('should get correct bid response', () => { + const result = spec.interpretResponse(bidResponse); + const validResponse = [{ + requestId: '30b31c1838de1e', + cpm: 3, + width: 300, + height: 250, + creativeId: 'test', + netRevenue: true, + dealId: '1', + currency: 'USD', + ttl: 900, + ad: 'tag' + }]; + + expect(result).to.deep.equal(validResponse); + }); + + it('handles nobid responses', () => { + let result = spec.interpretResponse(noBidResponse); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/yieldbotBidAdapter_spec.js b/test/spec/modules/yieldbotBidAdapter_spec.js index a29d441e751..206645acd95 100644 --- a/test/spec/modules/yieldbotBidAdapter_spec.js +++ b/test/spec/modules/yieldbotBidAdapter_spec.js @@ -1,518 +1,1326 @@ -import {expect} from 'chai'; -import YieldbotAdapter from 'modules/yieldbotBidAdapter'; -import bidManager from 'src/bidmanager'; -import adLoader from 'src/adloader'; -import {cloneJson} from 'src/utils'; - -const bidderRequest = { - bidderCode: 'yieldbot', - bidder: 'yieldbot', - bidderRequestId: '187a340cb9ccc5', - bids: [ - { - bidId: '2640ad280208cc', - sizes: [[300, 250], [300, 600]], - bidder: 'yieldbot', - bidderRequestId: '187a340cb9ccc0', - params: { psn: '1234', slot: 'medrec' }, - requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ec0', - placementCode: '/4294967296/adunit0' +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 { newAuctionManager } from 'src/auctionManager'; +import * as utils from 'src/utils'; +import * as urlUtils from 'src/url'; +import events from 'src/events'; +import { YieldbotAdapter, spec } from 'modules/yieldbotBidAdapter'; + +before(function() { + YieldbotAdapter.clearAllCookies(); +}); +describe('Yieldbot Adapter Unit Tests', function() { + const ALL_SEARCH_PARAMS = ['apie', 'bt', 'cb', 'cts_ad', 'cts_imp', 'cts_ini', 'cts_js', 'cts_ns', 'cts_rend', 'cts_res', 'e', 'ioa', 'it', 'la', 'lo', 'lpv', 'lpvi', 'mtp', 'np', 'pvd', 'pvi', 'r', 'ri', 'sb', 'sd', 'si', 'slot', 'sn', 'ssz', 'to', 'ua', 'v', 'vi']; + + const BID_LEADERBOARD_728x90 = { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'leaderboard' + }, + adUnitCode: '/0000000/leaderboard', + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c19', + sizes: [728, 90], + bidId: '2240b2af6064bb', + bidderRequestId: '1e878e3676fb85', + auctionId: 'c9964bd5-f835-4c91-916e-00295819f932' + }; + + const BID_MEDREC_300x600 = { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'medrec' + }, + adUnitCode: '/0000000/side-bar', + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c20', + sizes: [300, 600], + bidId: '332067957eaa33', + bidderRequestId: '1e878e3676fb85', + auctionId: 'c9964bd5-f835-4c91-916e-00295819f932' + }; + + const BID_MEDREC_300x250 = { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'medrec' + }, + adUnitCode: '/0000000/medrec', + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c21', + sizes: [[300, 250]], + bidId: '49d7fe5c3a15ed', + bidderRequestId: '1e878e3676fb85', + auctionId: 'c9964bd5-f835-4c91-916e-00295819f932' + }; + + const BID_SKY160x600 = { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'skyscraper' }, + adUnitCode: '/0000000/side-bar', + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c21', + sizes: [160, 600], + bidId: '49d7fe5c3a16ee', + bidderRequestId: '1e878e3676fb85', + auctionId: 'c9964bd5-f835-4c91-916e-00295819f932' + }; + + const AD_UNITS = [ { - bidId: '35751f10be5b6b', - sizes: [[728, 90], [970, 90]], - bidder: 'yieldbot', - bidderRequestId: '187a340cb9ccc1', - params: { psn: '1234', slot: 'leaderboard' }, - requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ec1', - placementCode: '/4294967296/adunit1' + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c19', + code: '/00000000/leaderboard', + sizes: [728, 90], + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'leaderboard' + } + } + ] }, { - bidId: '2640ad280208cd', + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c20', + code: '/00000000/medrec', sizes: [[300, 250]], - bidder: 'yieldbot', - bidderRequestId: '187a340cb9ccc2', - params: { psn: '1234', slot: 'medrec' }, - requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ec2', - placementCode: '/4294967296/adunit2' + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'medrec' + } + } + ] + }, + { + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c21', + code: '/00000000/multi-size', + sizes: [[300, 600]], + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'medrec' + } + } + ] }, - ] -}; - -const YB_BID_FIXTURE = { - medrec: { - ybot_ad: 'y', - ybot_slot: 'medrec', - ybot_cpm: '200', - ybot_size: '300x250' - }, - leaderboard: { - ybot_ad: 'n' - }, - noop: { - ybot_ad: 'y', - ybot_slot: 'noop', - ybot_cpm: '200', - ybot_size: '300x250' - } -}; - -function createYieldbotMockLib() { - window.yieldbot = { - _initialized: false, - pub: (psn) => {}, - defineSlot: (slotName, optionalDomIdOrConfigObject, optionalTime) => {}, - enableAsync: () => {}, - go: () => {}, - nextPageview: (slots, callback) => {}, - getSlotCriteria: (slotName) => {} + { + transactionId: '3bcca099-e22a-4e1e-ab60-365a74a87c22', + code: '/00000000/skyscraper', + sizes: [[160, 600]], + bids: [ + { + bidder: 'yieldbot', + params: { + psn: '1234', + slot: 'skyscraper' + } + } + ] + } + ]; + + const INTERPRET_RESPONSE_BID_REQUEST = { + method: 'GET', + url: '//i.yldbt.com/m/1234/v1/init', + data: { + cts_js: 1518184900582, + cts_ns: 1518184900582, + v: 'pbjs-yb-1.0.0', + vi: 'jdg00eijgpvemqlz73', + si: 'jdg00eil9y4mcdo850', + pvd: 6, + pvi: 'jdg03ai5kp9k1rkheh', + lpv: 1518184868108, + lpvi: 'jdg02lfwmdx8n0ncgc', + bt: 'init', + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36', + np: 'MacIntel', + la: 'en-US', + to: 5, + sd: '2560x1440', + lo: 'http://localhost:9999/test/spec/e2e/gpt-examples/gpt_yieldbot.html', + r: '', + e: '', + sn: 'leaderboard|medrec|medrec|skyscraper', + ssz: '728x90|300x250|300x600|160x600', + cts_ini: 1518184900591 + }, + yieldbotSlotParams: { + psn: '1234', + sn: 'leaderboard|medrec|medrec|skyscraper', + ssz: '728x90|300x250|300x600|160x600', + bidIdMap: { + 'jdg03ai5kp9k1rkheh:leaderboard:728x90': '2240b2af6064bb', + 'jdg03ai5kp9k1rkheh:medrec:300x250': '49d7fe5c3a15ed', + 'jdg03ai5kp9k1rkheh:medrec:300x600': '332067957eaa33', + 'jdg03ai5kp9k1rkheh:skyscraper:160x600': '49d7fe5c3a16ee' + } + }, + options: { + withCredentials: true, + customHeaders: { + Accept: 'application/json' + } + } }; -} -function restoreYieldbotMockLib() { - window.yieldbot = null; -} + const INTERPRET_RESPONSE_SERVER_RESPONSE = { + body: { + pvi: 'jdg03ai5kp9k1rkheh', + subdomain_iframe: 'ads-adseast-vpc', + url_prefix: 'http://ads-adseast-vpc.yldbt.com/m/', + slots: [ + { + slot: 'leaderboard', + cpm: '800', + size: '728x90' + }, + { + slot: 'medrec', + cpm: '300', + size: '300x250' + }, + { + slot: 'medrec', + cpm: '800', + size: '300x600' + }, + { + slot: 'skyscraper', + cpm: '300', + size: '160x600' + } + ], + user_syncs: [ + 'https://usersync.dd9693a32aa1.com/00000000.gif?p=a', + 'https://usersync.3b19503b37d8.com/00000000.gif?p=b', + 'https://usersync.5cb389d36d30.com/00000000.gif?p=c' + ] + }, + headers: {} + }; -function mockYieldbotBidRequest() { - window.ybotq = window.ybotq || []; - window.ybotq.forEach(fn => { - fn.apply(window.yieldbot); + let FIXTURE_AD_UNITS, FIXTURE_SERVER_RESPONSE, FIXTURE_BID_REQUEST, FIXTURE_BID_REQUESTS, FIXTURE_BIDS; + beforeEach(function() { + FIXTURE_AD_UNITS = utils.deepClone(AD_UNITS); + FIXTURE_BIDS = { + BID_LEADERBOARD_728x90: utils.deepClone(BID_LEADERBOARD_728x90), + BID_MEDREC_300x600: utils.deepClone(BID_MEDREC_300x600), + BID_MEDREC_300x250: utils.deepClone(BID_MEDREC_300x250), + BID_SKY160x600: utils.deepClone(BID_SKY160x600) + }; + + FIXTURE_BID_REQUEST = utils.deepClone(INTERPRET_RESPONSE_BID_REQUEST); + FIXTURE_SERVER_RESPONSE = utils.deepClone(INTERPRET_RESPONSE_SERVER_RESPONSE); + FIXTURE_BID_REQUESTS = [ + FIXTURE_BIDS.BID_LEADERBOARD_728x90, + FIXTURE_BIDS.BID_MEDREC_300x600, + FIXTURE_BIDS.BID_MEDREC_300x250, + FIXTURE_BIDS.BID_SKY160x600 + ]; }); - window.ybotq = []; -} - -const localSetupTestRegex = /localSetupTest$/; -const MAKE_BID_REQUEST = true; -let sandbox; -let bidManagerStub; -let yieldbotLibStub; - -/** - * Test initialization hook. Makes initial adapter and mock bid requests<br> - * unless the test is a special case with "localSetupTest". <br> - * 1. All suite tests are initialized with required mocks and stubs<br> - * 2. If the test title does <em>not</em> end in "localSetupTest", adapter and - * mock bid requests are executed - * 3. Test titles ending in "localSetupTest" are special case tests and are - * expected to call <code>setupTest(object, MAKE_BID_REQUEST)</code> where - * applicable - * @param {object} testRequest bidder request bids fixture - * @param {boolean} force trigger adapter callBids and Yieldbot library request - * @private - */ -function setupTest(testRequest, force = false) { - sandbox = sinon.sandbox.create(); - - createYieldbotMockLib(); - - sandbox.stub(adLoader, 'loadScript'); - - yieldbotLibStub = {}; - yieldbotLibStub.nextPageview = sandbox.stub(window.yieldbot, 'nextPageview'); - yieldbotLibStub.defineSlot = sandbox.stub(window.yieldbot, 'defineSlot'); - yieldbotLibStub.pub = sandbox.stub(window.yieldbot, 'pub'); - yieldbotLibStub.enableAsync = sandbox.stub(window.yieldbot, 'enableAsync'); - - yieldbotLibStub.getSlotCriteria = - sandbox.stub( - window.yieldbot, - 'getSlotCriteria', - (slotName) => { - return YB_BID_FIXTURE[slotName] || {ybot_ad: 'n'}; - }); - yieldbotLibStub.go = - sandbox.stub( - window.yieldbot, - 'go', - () => { - window.yieldbot._initialized = true; - }); - - bidManagerStub = sandbox.stub(bidManager, 'addBidResponse'); - - const ybAdapter = new YieldbotAdapter(); - let request = testRequest || cloneJson(bidderRequest); - if ((this && !this.currentTest.parent.title.match(localSetupTestRegex)) || force === MAKE_BID_REQUEST) { - ybAdapter.callBids(request); - mockYieldbotBidRequest(); - } - return { adapter: ybAdapter, localRequest: request }; -} - -function restoreTest() { - sandbox.restore(); - restoreYieldbotMockLib(); -} - -describe('Yieldbot adapter tests', function() { - let adapter; - let localRequest; - beforeEach(function () { - const testSetupCtx = setupTest.call(this); - adapter = testSetupCtx.adapter; - localRequest = testSetupCtx.localRequest; + afterEach(function() { + YieldbotAdapter._optOut = false; + YieldbotAdapter.clearAllCookies(); + YieldbotAdapter._isInitialized = false; + YieldbotAdapter.initialize(); }); - afterEach(function() { - restoreTest(); + describe('Adapter exposes BidderSpec API', function() { + it('code', function() { + expect(spec.code).to.equal('yieldbot'); + }); + it('supportedMediaTypes', function() { + expect(spec.supportedMediaTypes).to.deep.equal(['banner']); + }); + it('isBidRequestValid', function() { + expect(spec.isBidRequestValid).to.be.a('function'); + }); + it('buildRequests', function() { + expect(spec.buildRequests).to.be.a('function'); + }); + it('interpretResponse', function() { + expect(spec.interpretResponse).to.be.a('function'); + }); }); - describe('getUniqueSlotSizes', function() { - it('should return [] for string sizes', function() { - const sizes = adapter.getUniqueSlotSizes('widthxheight'); - expect(sizes).to.deep.equal([]); + describe('isBidRequestValid', function() { + let bid = { + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: 'bar' + }, + sizes: [[300, 250], [300, 600]] + }; + + it('valid parameters', function() { + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: 'bar' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(true); }); - it('should return [] for Object sizes', function() { - const sizes = adapter.getUniqueSlotSizes({width: 300, height: 250}); - expect(sizes).to.deep.equal([]); + it('undefined parameters', function() { + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + slot: 'bar' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); }); - it('should return [] for boolean sizes', function() { - const sizes = adapter.getUniqueSlotSizes(true); - expect(sizes).to.deep.equal([]); + it('falsey string parameters', function() { + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: '', + slot: 'bar' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: '' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: 0 + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); }); - it('should return [] for undefined sizes', function() { - const sizes = adapter.getUniqueSlotSizes(undefined); - expect(sizes).to.deep.equal([]); + it('parameters type invalid', function() { + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: 0 + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: { name: 'foo' }, + slot: 'bar' + }, + sizes: [[300, 250], [300, 600]] + })).to.equal(false); }); - it('should return [] for function sizes', function() { - const sizes = adapter.getUniqueSlotSizes(function () {}); - expect(sizes).to.deep.equal([]); + it('invalid sizes type', function() { + expect(spec.isBidRequestValid({ + bidder: 'yieldbot', + 'params': { + psn: 'foo', + slot: 'bar' + }, + sizes: {} + })).to.equal(true); }); + }); - it('should return [] for number sizes', function() { - const sizes = adapter.getUniqueSlotSizes(1111); - expect(sizes).to.deep.equal([]); + describe('getSlotRequestParams', function() { + const EMPTY_SLOT_PARAMS = { sn: '', ssz: '', bidIdMap: {} }; + + it('should default to empty slot params', function() { + expect(YieldbotAdapter.getSlotRequestParams('')).to.deep.equal(EMPTY_SLOT_PARAMS); + expect(YieldbotAdapter.getSlotRequestParams()).to.deep.equal(EMPTY_SLOT_PARAMS); + expect(YieldbotAdapter.getSlotRequestParams('', [])).to.deep.equal(EMPTY_SLOT_PARAMS); + expect(YieldbotAdapter.getSlotRequestParams(0, [])).to.deep.equal(EMPTY_SLOT_PARAMS); }); - it('should return [] for array of numbers', function() { - const sizes = adapter.getUniqueSlotSizes([300, 250]); - expect(sizes).to.deep.equal([]); + it('should build slot bid request parameters', function() { + const bidRequests = [ + FIXTURE_BIDS.BID_LEADERBOARD_728x90, + FIXTURE_BIDS.BID_MEDREC_300x600, + FIXTURE_BIDS.BID_MEDREC_300x250 + ]; + const slotParams = YieldbotAdapter.getSlotRequestParams('f0e1d2c', bidRequests); + + expect(slotParams.psn).to.equal('1234'); + expect(slotParams.sn).to.equal('leaderboard|medrec'); + expect(slotParams.ssz).to.equal('728x90|300x600.300x250'); + + let bidId = slotParams.bidIdMap['f0e1d2c:leaderboard:728x90']; + expect(bidId).to.equal('2240b2af6064bb'); + + bidId = slotParams.bidIdMap['f0e1d2c:medrec:300x250']; + expect(bidId).to.equal('49d7fe5c3a15ed'); + + bidId = slotParams.bidIdMap['f0e1d2c:medrec:300x600']; + expect(bidId).to.equal('332067957eaa33'); }); - it('should return array of unique strings', function() { - const sizes = adapter.getUniqueSlotSizes(['300x250', '300x600', '728x90', '300x250']); - expect(sizes).to.deep.equal([['300', '250'], ['300', '600'], ['728', '90']]); + it('should build slot bid request parameters in order of bidRequests', function() { + const bidRequests = [ + FIXTURE_BIDS.BID_MEDREC_300x600, + FIXTURE_BIDS.BID_LEADERBOARD_728x90, + FIXTURE_BIDS.BID_MEDREC_300x250 + ]; + + const slotParams = YieldbotAdapter.getSlotRequestParams('f0e1d2c', bidRequests); + + expect(slotParams.psn).to.equal('1234'); + expect(slotParams.sn).to.equal('medrec|leaderboard'); + expect(slotParams.ssz).to.equal('300x600.300x250|728x90'); + + let bidId = slotParams.bidIdMap['f0e1d2c:leaderboard:728x90']; + expect(bidId).to.equal('2240b2af6064bb'); + + bidId = slotParams.bidIdMap['f0e1d2c:medrec:300x250']; + expect(bidId).to.equal('49d7fe5c3a15ed'); + + bidId = slotParams.bidIdMap['f0e1d2c:medrec:300x600']; + expect(bidId).to.equal('332067957eaa33'); }); - it('should return array of unique strings for string elements only', function() { - const sizes = adapter.getUniqueSlotSizes(['300x250', ['threexfour']]); - expect(sizes).to.deep.equal([['300', '250']]); + it('should exclude slot bid requests with malformed sizes', function() { + const bid = FIXTURE_BIDS.BID_MEDREC_300x250; + bid.sizes = ['300x250']; + const bidRequests = [bid, FIXTURE_BIDS.BID_LEADERBOARD_728x90]; + const slotParams = YieldbotAdapter.getSlotRequestParams('affffffe', bidRequests); + expect(slotParams.psn).to.equal('1234'); + expect(slotParams.sn).to.equal('leaderboard'); + expect(slotParams.ssz).to.equal('728x90'); }); + }); - it('should return array of unique strings, including non-numeric', function() { - const sizes = adapter.getUniqueSlotSizes(['300x250', 'threexfour', 'fivexsix']); - expect(sizes).to.deep.equal([['300', '250'], ['three', 'four'], ['five', 'si']]); + describe('getCookie', function() { + it('should return null if cookie name not found', function() { + const cookieName = YieldbotAdapter.newId(); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); }); }); - describe('callBids', function() { - it('should request the yieldbot library', function() { - sinon.assert.calledOnce(adLoader.loadScript); - sinon.assert.calledWith(adLoader.loadScript, '//cdn.yldbt.com/js/yieldbot.intent.js'); + describe('setCookie', function() { + it('should set a root path first-party cookie with temporal expiry', function() { + const cookieName = YieldbotAdapter.newId(); + const cookieValue = YieldbotAdapter.newId(); + + YieldbotAdapter.setCookie(cookieName, cookieValue, 2000, '/'); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(cookieValue); + + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); }); - it('should set a yieldbot psn', function() { - sinon.assert.called(yieldbotLibStub.pub); - sinon.assert.calledWith(yieldbotLibStub.pub, '1234'); + it('should set a root path first-party cookie with session expiry', function() { + const cookieName = YieldbotAdapter.newId(); + const cookieValue = YieldbotAdapter.newId(); + + YieldbotAdapter.setCookie(cookieName, cookieValue, null, '/'); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(cookieValue); + + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); }); - it('should not repeat multiply defined slot sizes', function() { - sinon.assert.calledTwice(yieldbotLibStub.defineSlot); - sinon.assert.neverCalledWith(yieldbotLibStub.defineSlot, 'medrec', {sizes: [['300', '250'], ['300', '600'], ['300', '250']]}); + it('should fail to set a cookie x-domain', function() { + const cookieName = YieldbotAdapter.newId(); + const cookieValue = YieldbotAdapter.newId(); + + YieldbotAdapter.setCookie(cookieName, cookieValue, null, '/', `${cookieName}.com`); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); }); + }); + + describe('clearAllcookies', function() { + it('should delete all first-party cookies', function() { + let idx, cookieLabels = YieldbotAdapter._cookieLabels, cookieName, cookieValue; + for (idx = 0; idx < cookieLabels.length; idx++) { + YieldbotAdapter.deleteCookie('__ybot' + cookieLabels[idx]); + } + + YieldbotAdapter.sessionBlocked = true; + expect(YieldbotAdapter.sessionBlocked, 'sessionBlocked').to.equal(true); + + const userId = YieldbotAdapter.userId; + expect(YieldbotAdapter.userId, 'userId').to.equal(userId); + + const sessionId = YieldbotAdapter.sessionId; + expect(YieldbotAdapter.sessionId, 'sessionId').to.equal(sessionId); + + const pageviewDepth = YieldbotAdapter.pageviewDepth; + expect(pageviewDepth, 'returned pageviewDepth').to.equal(1); + expect(YieldbotAdapter.pageviewDepth, 'get pageviewDepth').to.equal(2); + + const lastPageviewId = YieldbotAdapter.newId(); + YieldbotAdapter.lastPageviewId = lastPageviewId; + expect(YieldbotAdapter.lastPageviewId, 'get lastPageviewId').to.equal(lastPageviewId); + + const lastPageviewTime = Date.now(); + YieldbotAdapter.lastPageviewTime = lastPageviewTime; + expect(YieldbotAdapter.lastPageviewTime, 'lastPageviewTime').to.equal(lastPageviewTime); - it('should define yieldbot slots', function() { - sinon.assert.calledTwice(yieldbotLibStub.defineSlot); - sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'medrec', {sizes: [['300', '250'], ['300', '600']]}); - sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'leaderboard', {sizes: [['728', '90'], ['970', '90']]}); + const urlPrefix = YieldbotAdapter.urlPrefix('http://here.there.com/ad/'); + expect(YieldbotAdapter.urlPrefix(), 'urlPrefix').to.equal('http://here.there.com/ad/'); + + for (idx = 0; idx < cookieLabels.length; idx++) { + cookieValue = YieldbotAdapter.getCookie('__ybot' + cookieLabels[idx]); + expect(!!cookieValue, 'setter: ' + cookieLabels[idx]).to.equal(true); + } + + YieldbotAdapter.clearAllCookies(); + + for (idx = 0; idx < cookieLabels.length; idx++) { + cookieName = '__ybot' + cookieLabels[idx]; + cookieValue = YieldbotAdapter.getCookie(cookieName); + expect(cookieValue, cookieName).to.equal(null); + }; }); + }); - it('should not use inherited Object properties, localSetupTest', function() { - let oProto = Object.prototype; - oProto.superProp = ['300', '250']; + describe('sessionBlocked', function() { + const cookieName = '__ybotn'; + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); - expect(Object.prototype.superProp).to.be.an('array'); - localRequest.bids.forEach((bid) => { - expect(bid.superProp).to.be.an('array'); - }); + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); - expect(YB_BID_FIXTURE.medrec.superProp).to.deep.equal(['300', '250']); - expect(YB_BID_FIXTURE.leaderboard.superProp).to.deep.equal(['300', '250']); + it('should return true if cookie value is interpreted as non-zero', function() { + YieldbotAdapter.setCookie(cookieName, '1', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string "1"').to.equal(true); - restoreTest(); - setupTest(localRequest, MAKE_BID_REQUEST); + YieldbotAdapter.setCookie(cookieName, '10.01', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string "10.01"').to.equal(true); - sinon.assert.neverCalledWith(yieldbotLibStub.defineSlot, 'superProp', {sizes: ['300', '250']}); - sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'medrec', {sizes: [['300', '250'], ['300', '600']]}); - sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'leaderboard', {sizes: [['728', '90'], ['970', '90']]}); + YieldbotAdapter.setCookie(cookieName, '-10.01', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string "-10.01"').to.equal(true); - delete oProto.superProp; - expect(Object.prototype.superProp).to.be.an('undefined'); + YieldbotAdapter.setCookie(cookieName, 1, 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the number 1').to.equal(true); }); - it('should enable yieldbot async mode', function() { - sinon.assert.called(yieldbotLibStub.enableAsync); + it('should return false if cookie name not found', function() { + expect(YieldbotAdapter.sessionBlocked).to.equal(false); }); - it('should add bid response after yieldbot request callback', function() { - const plc1 = bidManagerStub.firstCall.args[0]; - expect(plc1).to.equal(localRequest.bids[0].placementCode); + it('should return false if cookie value is interpreted as zero', function() { + YieldbotAdapter.setCookie(cookieName, '0', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string "0"').to.equal(false); - const pb_bid1 = bidManagerStub.firstCall.args[1]; - expect(pb_bid1.bidderCode).to.equal('yieldbot'); - expect(pb_bid1.cpm).to.equal(2); - expect(pb_bid1.ybot_ad).to.equal('y'); - expect(pb_bid1.ybot_slot).to.equal('medrec'); - expect(pb_bid1.ybot_cpm).to.equal('200'); - expect(pb_bid1.ybot_size).to.equal('300x250'); + YieldbotAdapter.setCookie(cookieName, '.01', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string ".01"').to.equal(false); - expect(pb_bid1.width).to.equal('300'); - expect(pb_bid1.height).to.equal('250'); - expect(pb_bid1.ad).to.match(/src="\/\/cdn\.yldbt\.com\/js\/yieldbot\.intent\.js/); - expect(pb_bid1.ad).to.match(/yieldbot\.renderAd\('medrec:300x250'\)/); + YieldbotAdapter.setCookie(cookieName, '-.9', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the string "-.9"').to.equal(false); - const plc2 = bidManagerStub.secondCall.args[0]; - expect(plc2).to.equal(localRequest.bids[1].placementCode); + YieldbotAdapter.setCookie(cookieName, 0, 2000, '/'); + expect(YieldbotAdapter.sessionBlocked, 'cookie value: the number 0').to.equal(false); + }); - const pb_bid2 = bidManagerStub.secondCall.args[1]; - expect(pb_bid2.bidderCode).to.equal('yieldbot'); - expect(pb_bid2.width).to.equal(0); - expect(pb_bid2.height).to.equal(0); - expect(pb_bid2.statusMessage).to.match(/empty.*response/); + it('should return false if cookie value source is a non-numeric string', function() { + YieldbotAdapter.setCookie(cookieName, 'true', 2000, '/'); + expect(YieldbotAdapter.sessionBlocked).to.equal(false); }); - it('should validate slot dimensions, localSetupTest', function() { - let invalidSizeBid = { - bidId: '2640ad280208ce', - sizes: [[728, 90], [300, 250], [970, 90]], - bidder: 'yieldbot', - bidderRequestId: '187a340cb9ccc3', - params: { psn: '1234', slot: 'medrec' }, - requestId: '5f297a1f-3163-46c2-854f-b55fd2e74ec3', - placementCode: '/4294967296/adunit3' - }; + it('should return false if cookie value source is a boolean', function() { + YieldbotAdapter.setCookie(cookieName, true, 2000, '/'); + expect(YieldbotAdapter.sessionBlocked).to.equal(false); + }); - const bidResponseMedrec = { - bidderCode: 'yieldbot', - width: '300', - height: '250', - statusMessage: 'Bid available', - cpm: 2, - ybot_ad: 'y', - ybot_slot: 'medrec', - ybot_cpm: '200', - ybot_size: '300x250' - }; + it('should set sessionBlocked', function() { + YieldbotAdapter.sessionBlocked = true; + expect(YieldbotAdapter.sessionBlocked).to.equal(true); + YieldbotAdapter.sessionBlocked = false; + expect(YieldbotAdapter.sessionBlocked).to.equal(false); + + YieldbotAdapter.sessionBlocked = 1; + expect(YieldbotAdapter.sessionBlocked).to.equal(true); + YieldbotAdapter.sessionBlocked = 0; + expect(YieldbotAdapter.sessionBlocked).to.equal(false); + + YieldbotAdapter.sessionBlocked = '1'; + expect(YieldbotAdapter.sessionBlocked).to.equal(true); + YieldbotAdapter.sessionBlocked = ''; + expect(YieldbotAdapter.sessionBlocked).to.equal(false); + }); + }); - localRequest.bids = [invalidSizeBid]; - restoreTest(); - setupTest(localRequest, MAKE_BID_REQUEST); + describe('userId', function() { + const cookieName = '__ybotu'; + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); - let bidManagerFirstCall = bidManagerStub.firstCall; + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); - expect(bidManagerFirstCall.args[0]).to.equal('/4294967296/adunit3'); - expect(bidManagerFirstCall.args[1]).to.include(bidResponseMedrec); + it('should set a user Id if cookie does not exist', function() { + const userId = YieldbotAdapter.userId; + expect(userId).to.match(/[0-9a-z]{18}/); }); - it('should make slot bid available once only', function() { - const bidResponseMedrec = { - bidderCode: 'yieldbot', - width: '300', - height: '250', - statusMessage: 'Bid available', - cpm: 2, - ybot_ad: 'y', - ybot_slot: 'medrec', - ybot_cpm: '200', - ybot_size: '300x250' - }; + it('should return user Id if cookie exists', function() { + const expectedUserId = YieldbotAdapter.newId(); + YieldbotAdapter.setCookie(cookieName, expectedUserId, 2000, '/'); + const userId = YieldbotAdapter.userId; + expect(userId).to.equal(expectedUserId); + }); + }); - const bidResponseNone = { - bidderCode: 'yieldbot', - width: 0, - height: 0, - statusMessage: 'Bid returned empty or error response' - }; + describe('sessionId', function() { + const cookieName = '__ybotsi'; + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); - let firstCall = bidManagerStub.firstCall; - let secondCall = bidManagerStub.secondCall; - let thirdCall = bidManagerStub.thirdCall; + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); - expect(firstCall.args[0]).to.equal('/4294967296/adunit0'); - expect(firstCall.args[1]).to.include(bidResponseMedrec); + it('should set a session Id if cookie does not exist', function() { + const sessionId = YieldbotAdapter.sessionId; + expect(sessionId).to.match(/[0-9a-z]{18}/); + }); + + it('should return session Id if cookie exists', function() { + const expectedSessionId = YieldbotAdapter.newId(); + YieldbotAdapter.setCookie(cookieName, expectedSessionId, 2000, '/'); + const sessionId = YieldbotAdapter.sessionId; + expect(sessionId).to.equal(expectedSessionId); + }); + }); + + describe('lastPageviewId', function() { + const cookieName = '__ybotlpvi'; + + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); + + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); - expect(secondCall.args[0]).to.equal('/4294967296/adunit1'); - expect(secondCall.args[1]).to.include(bidResponseNone); + it('should return empty string if cookie does not exist', function() { + const lastBidId = YieldbotAdapter.lastPageviewId; + expect(lastBidId).to.equal(''); + }); - expect(thirdCall.args[0]).to.equal('/4294967296/adunit2'); - expect(thirdCall.args[1]).to.include(bidResponseNone); + it('should set an id string', function() { + const id = YieldbotAdapter.newId(); + YieldbotAdapter.lastPageviewId = id; + const lastBidId = YieldbotAdapter.lastPageviewId; + expect(lastBidId).to.equal(id); }); }); - describe('callBids, refresh', function() { - it('should use yieldbot.nextPageview after first callBids', function() { - expect(window.yieldbot._initialized).to.equal(true); + describe('lastPageviewTime', function() { + const cookieName = '__ybotlpv'; + + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); + + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); - adapter.callBids(localRequest); - mockYieldbotBidRequest(); - sinon.assert.calledOnce(yieldbotLibStub.nextPageview); + it('should return zero if cookie does not exist', function() { + const lastBidTime = YieldbotAdapter.lastPageviewTime; + expect(lastBidTime).to.equal(0); }); - it('should call yieldbot.nextPageview with slot config of requested bids', function() { - expect(window.yieldbot._initialized).to.equal(true); - sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'medrec'); - sinon.assert.calledWith(yieldbotLibStub.defineSlot, 'leaderboard'); + it('should set a timestamp', function() { + const ts = Date.now(); + YieldbotAdapter.lastPageviewTime = ts; + const lastBidTime = YieldbotAdapter.lastPageviewTime; + expect(lastBidTime).to.equal(ts); + }); + }); - const refreshBids = localRequest.bids.filter((object) => { return object.placementCode === '/4294967296/adunit1'; }); - let refreshRequest = cloneJson(localRequest); - refreshRequest.bids = refreshBids; - expect(refreshRequest.bids.length).to.equal(1); + describe('pageviewDepth', function() { + const cookieName = '__ybotpvd'; - adapter.callBids(refreshRequest); - mockYieldbotBidRequest(); + beforeEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + }); - const expectedSlots = { 'leaderboard': [['728', '90'], ['970', '90']] }; + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); - sinon.assert.calledWithExactly(yieldbotLibStub.nextPageview, expectedSlots); + it('should return one (1) if cookie does not exist', function() { + const pageviewDepth = YieldbotAdapter.pageviewDepth; + expect(pageviewDepth).to.equal(1); }); - it('should not repeat multiply defined slot sizes', function() { - // placementCode: '/4294967296/adunit0' - // placementCode: '/4294967296/adunit2' - // Both placements declare medrec:300x250 - adapter.callBids(localRequest); - mockYieldbotBidRequest(); + it('should increment the integer string for depth', function() { + let pageviewDepth = YieldbotAdapter.pageviewDepth; + expect(pageviewDepth).to.equal(1); - sinon.assert.calledOnce(yieldbotLibStub.nextPageview); - const expectedSlots = { 'leaderboard': [['728', '90'], ['970', '90']], 'medrec': [['300', '250'], ['300', '600']]}; - sinon.assert.calledWithExactly(yieldbotLibStub.nextPageview, expectedSlots); + pageviewDepth = YieldbotAdapter.pageviewDepth; + expect(pageviewDepth).to.equal(2); }); + }); - it('should not add empty bidResponse on callBids without bidsRequested', function() { - expect(window.yieldbot._initialized).to.equal(true); - expect(bidManagerStub.calledThrice).to.equal(true); + describe('urlPrefix', function() { + const cookieName = '__ybotc'; + const protocol = document.location.protocol; + afterEach(function() { + YieldbotAdapter.deleteCookie(cookieName); + expect(YieldbotAdapter.getCookie(cookieName)).to.equal(null); + }); - adapter.callBids({}); - mockYieldbotBidRequest(); + it('should set the default prefix if cookie does not exist', function(done) { + const urlPrefix = YieldbotAdapter.urlPrefix(); + expect(urlPrefix).to.equal('//i.yldbt.com/m/'); + done(); + }); - expect(bidManagerStub.calledThrice).to.equal(true); // the initial bids - sinon.assert.notCalled(yieldbotLibStub.nextPageview); + it('should return prefix if cookie exists', function() { + YieldbotAdapter.setCookie(cookieName, protocol + '//closest.az.com/path/', 2000, '/'); + const urlPrefix = YieldbotAdapter.urlPrefix(); + expect(urlPrefix).to.equal(protocol + '//closest.az.com/path/'); }); - it('should validate slot dimensions', function() { - localRequest.bids.map(bid => { - bid.sizes = [[640, 480], [1024, 768]]; + it('should reset prefix if default already set', function() { + const defaultUrlPrefix = YieldbotAdapter.urlPrefix(); + const url = protocol + '//close.az.com/path/'; + expect(defaultUrlPrefix).to.equal('//i.yldbt.com/m/'); + + let urlPrefix = YieldbotAdapter.urlPrefix(url); + expect(urlPrefix, 'reset prefix').to.equal(url); + + urlPrefix = YieldbotAdapter.urlPrefix(); + expect(urlPrefix, 'subsequent request').to.equal(url); + }); + + it('should set containing document protocol', function() { + let urlPrefix = YieldbotAdapter.urlPrefix('http://close.az.com/path/'); + expect(urlPrefix, 'http - use: ' + protocol).to.equal(protocol + '//close.az.com/path/'); + + urlPrefix = YieldbotAdapter.urlPrefix('https://close.az.com/path/'); + expect(urlPrefix, 'https - use: ' + protocol).to.equal(protocol + '//close.az.com/path/'); + }); + + it('should fallback to default for invalid argument', function() { + let urlPrefix = YieldbotAdapter.urlPrefix('//close.az.com/path/'); + expect(urlPrefix, 'Url w/o protocol').to.equal('//i.yldbt.com/m/'); + + urlPrefix = YieldbotAdapter.urlPrefix('mumble'); + expect(urlPrefix, 'Invalid Url').to.equal('//i.yldbt.com/m/'); + }); + }); + + describe('initBidRequestParams', function() { + it('should build common bid request state parameters', function() { + const params = YieldbotAdapter.initBidRequestParams( + [ + { + 'params': { + psn: '1234', + slot: 'medrec' + }, + sizes: [[300, 250], [300, 600]] + } + ] + ); + + const expectedParamKeys = [ + 'v', + 'vi', + 'si', + 'pvi', + 'pvd', + 'lpvi', + 'bt', + 'lo', + 'r', + 'sd', + 'to', + 'la', + 'np', + 'ua', + 'lpv', + 'cts_ns', + 'cts_js', + 'e' + ]; + + const missingKeys = []; + expectedParamKeys.forEach((item) => { + if (item in params === false) { + missingKeys.push(item); + } + }); + const extraKeys = []; + Object.keys(params).forEach((item) => { + if (!find(expectedParamKeys, param => param === item)) { + extraKeys.push(item); + } }); - const bidResponseNone = { - bidderCode: 'yieldbot', - width: 0, - height: 0, - statusMessage: 'Bid returned empty or error response' + expect( + missingKeys.length, + `\nExpected: ${expectedParamKeys}\nMissing keys: ${JSON.stringify(missingKeys)}`) + .to.equal(0); + expect( + extraKeys.length, + `\nExpected: ${expectedParamKeys}\nExtra keys: ${JSON.stringify(extraKeys)}`) + .to.equal(0); + }); + }); + + describe('buildRequests', function() { + it('should not return bid requests if optOut', function() { + YieldbotAdapter._optOut = true; + const requests = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS); + expect(requests.length).to.equal(0); + }); + + it('should not return bid requests if sessionBlocked', function() { + YieldbotAdapter.sessionBlocked = true; + const requests = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS); + expect(requests.length).to.equal(0); + YieldbotAdapter.sessionBlocked = false; + }); + + it('should re-enable requests when sessionBlocked expires', function() { + const cookieName = '__ybotn'; + YieldbotAdapter.setCookie( + cookieName, + 1, + 2000, + '/'); + let requests = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS); + expect(requests.length).to.equal(0); + YieldbotAdapter.deleteCookie(cookieName); + requests = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS); + expect(requests.length).to.equal(1); + }); + + it('should return a single BidRequest object', function() { + const requests = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS); + expect(requests.length).to.equal(1); + }); + + it('should have expected server options', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + const expectedOptions = { + withCredentials: true, + customHeaders: { + Accept: 'application/json' + } }; + expect(request.options).to.eql(expectedOptions); + }); - adapter.callBids(localRequest); - mockYieldbotBidRequest(); + it('should be a GET request', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.method).to.equal('GET'); + }); - expect(bidManagerStub.getCalls().length).to.equal(6); + it('should have bid request specific params', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.data).to.not.equal(undefined); + + const expectedParamKeys = [ + 'v', + 'vi', + 'si', + 'pvi', + 'pvd', + 'lpvi', + 'bt', + 'lo', + 'r', + 'sd', + 'to', + 'la', + 'np', + 'ua', + 'sn', + 'ssz', + 'lpv', + 'cts_ns', + 'cts_js', + 'cts_ini', + 'e' + ]; + + const missingKeys = []; + expectedParamKeys.forEach((item) => { + if (item in request.data === false) { + missingKeys.push(item); + } + }); + const extraKeys = []; + Object.keys(request.data).forEach((item) => { + if (!find(expectedParamKeys, param => param === item)) { + extraKeys.push(item); + } + }); - let lastNextPageview = yieldbotLibStub.nextPageview.lastCall; - let nextPageviewSlots = lastNextPageview.args[0]; - expect(nextPageviewSlots.medrec).to.deep.equal([['640', '480'], ['1024', '768']]); - expect(nextPageviewSlots.leaderboard).to.deep.equal([['640', '480'], ['1024', '768']]); + expect( + missingKeys.length, + `\nExpected: ${expectedParamKeys}\nMissing keys: ${JSON.stringify(missingKeys)}`) + .to.equal(0); + expect( + extraKeys.length, + `\nExpected: ${expectedParamKeys}\nExtra keys: ${JSON.stringify(extraKeys)}`) + .to.equal(0); + }); - let fourthCall = bidManagerStub.getCall(3); - let fifthCall = bidManagerStub.getCall(4); - let sixthCall = bidManagerStub.getCall(5); + it('should have the correct bidUrl form', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + const bidUrl = '//i.yldbt.com/m/1234/v1/init'; + expect(request.url).to.equal(bidUrl); + }); - expect(fourthCall.args[0]).to.equal('/4294967296/adunit0'); - expect(fourthCall.args[1]).to.include(bidResponseNone); + it('should set the bid request slot/bidId mapping', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.yieldbotSlotParams).to.not.equal(undefined); + expect(request.yieldbotSlotParams.bidIdMap).to.not.equal(undefined); + + const map = {}; + map[request.data.pvi + ':leaderboard:728x90'] = '2240b2af6064bb'; + map[request.data.pvi + ':medrec:300x250'] = '49d7fe5c3a15ed'; + map[request.data.pvi + ':medrec:300x600'] = '332067957eaa33'; + map[request.data.pvi + ':skyscraper:160x600'] = '49d7fe5c3a16ee'; + expect(request.yieldbotSlotParams.bidIdMap).to.eql(map); + }); - expect(fifthCall.args[0]).to.equal('/4294967296/adunit1'); - expect(fifthCall.args[1]).to.include(bidResponseNone); + it('should set the bid request publisher number', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.yieldbotSlotParams.psn).to.equal('1234'); + }); - expect(sixthCall.args[0]).to.equal('/4294967296/adunit2'); - expect(sixthCall.args[1]).to.include(bidResponseNone); + it('should have unique slot name parameter', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.yieldbotSlotParams.sn).to.equal('leaderboard|medrec|skyscraper'); }); - it('should not make requests for previously requested bids', function() { - const bidResponseMedrec = { - bidderCode: 'yieldbot', - width: '300', - height: '250', - statusMessage: 'Bid available', - cpm: 2, - ybot_ad: 'y', - ybot_slot: 'medrec', - ybot_cpm: '200', - ybot_size: '300x250' - }; + it('should have slot sizes parameter', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.yieldbotSlotParams.ssz).to.equal('728x90|300x600.300x250|160x600'); + }); - const bidResponseNone = { - bidderCode: 'yieldbot', - width: 0, - height: 0, - statusMessage: 'Bid returned empty or error response' - }; + it('should use edge server Url prefix if set', function() { + const cookieName = '__ybotc'; + YieldbotAdapter.setCookie( + cookieName, + 'http://close.edge.adserver.com/', + 2000, + '/'); - // Refresh #1 - adapter.callBids(localRequest); - mockYieldbotBidRequest(); + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + expect(request.url).to.match(/^http:\/\/close\.edge\.adserver\.com\//); + }); - expect(bidManagerStub.getCalls().length).to.equal(6); + it('should be adapter loaded before navigation start time', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + const timeDiff = request.data.cts_ns - request.data.cts_js; + expect(timeDiff >= 0, 'adapter loaded < nav').to.equal(true); + }); - let lastNextPageview = yieldbotLibStub.nextPageview.lastCall; - let nextPageviewSlots = lastNextPageview.args[0]; - expect(nextPageviewSlots.medrec).to.deep.equal([['300', '250'], ['300', '600']]); - expect(nextPageviewSlots.leaderboard).to.deep.equal([['728', '90'], ['970', '90']]); + it('should be navigation start before bid request time', function() { + const request = YieldbotAdapter.buildRequests(FIXTURE_BID_REQUESTS)[0]; + const timeDiff = request.data.cts_ini - request.data.cts_ns; + expect(timeDiff >= 0, 'nav start < request').to.equal(true); + }); + }); - let fourthCall = bidManagerStub.getCall(3); - let fifthCall = bidManagerStub.getCall(4); - let sixthCall = bidManagerStub.getCall(5); + describe('interpretResponse', function() { + it('should not return Bids if optOut', function() { + YieldbotAdapter._optOut = true; + const responses = YieldbotAdapter.interpretResponse(); + expect(responses.length).to.equal(0); + }); + + it('should not return Bids if no server response slot bids', function() { + FIXTURE_SERVER_RESPONSE.body.slots = []; + const responses = YieldbotAdapter.interpretResponse(FIXTURE_SERVER_RESPONSE, FIXTURE_BID_REQUEST); + expect(responses.length).to.equal(0); + }); - expect(fourthCall.args[0]).to.equal('/4294967296/adunit0'); - expect(fourthCall.args[1]).to.include(bidResponseMedrec); + it('should not include Bid if missing cpm', function() { + delete FIXTURE_SERVER_RESPONSE.body.slots[1].cpm; + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses.length).to.equal(3); + }); - expect(fifthCall.args[0]).to.equal('/4294967296/adunit1'); - expect(fifthCall.args[1]).to.include(bidResponseNone); + it('should not include Bid if missing size', function() { + delete FIXTURE_SERVER_RESPONSE.body.slots[2].size; + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses.length).to.equal(3); + }); - expect(sixthCall.args[0]).to.equal('/4294967296/adunit2'); - expect(sixthCall.args[1]).to.include(bidResponseNone); + it('should not include Bid if missing slot', function() { + delete FIXTURE_SERVER_RESPONSE.body.slots[3].slot; + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses.length).to.equal(3); + }); - localRequest.bids.map(bid => { - bid.sizes = [[640, 480], [1024, 768]]; + it('should have a valid creativeId', function() { + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses.length).to.equal(4); + responses.forEach((bid) => { + expect(bid.creativeId).to.match(/[0-9a-z]{18}/); + const containerDivId = 'ybot-' + bid.creativeId; + const re = new RegExp(containerDivId); + expect(re.test(bid.ad)).to.equal(true); }); - let bidForNinethCall = localRequest.bids[localRequest.bids.length - 1]; - bidForNinethCall.sizes = [[300, 250]]; + }); + + it('should specify Net revenue type for bid', function() { + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses[0].netRevenue).to.equal(true); + }); + + it('should specify USD currency for bid', function() { + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses[1].currency).to.equal('USD'); + }); - // Refresh #2 - adapter.callBids(localRequest); - mockYieldbotBidRequest(); + it('should set edge server Url prefix', function() { + FIXTURE_SERVER_RESPONSE.body.url_prefix = 'http://close.edge.adserver.com/'; + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + const edgeServerUrlPrefix = YieldbotAdapter.getCookie('__ybotc'); + + const protocol = document.location.protocol; + const beginsRegex = new RegExp('^' + protocol + '\/\/close\.edge\.adserver\.com\/'); + const containsRegex = new RegExp(protocol + '\/\/close\.edge\.adserver\.com\/'); + expect(edgeServerUrlPrefix).to.match(beginsRegex); + expect(responses[0].ad).to.match(containsRegex); + }); + }); + + describe('getUserSyncs', function() { + let responses; + beforeEach(function () { + responses = [FIXTURE_SERVER_RESPONSE]; + }); + it('should return empty Array when server response property missing', function() { + delete responses[0].body.user_syncs; + const userSyncs = YieldbotAdapter.getUserSyncs({ pixelEnabled: true }, responses); + expect(userSyncs.length).to.equal(0); + }); + + it('should return empty Array when server response property is empty', function() { + responses[0].body.user_syncs = []; + const userSyncs = YieldbotAdapter.getUserSyncs({ pixelEnabled: true }, responses); + expect(userSyncs.length).to.equal(0); + }); - expect(bidManagerStub.getCalls().length).to.equal(9); + it('should return empty Array pixel disabled', function() { + const userSyncs = YieldbotAdapter.getUserSyncs({ pixelEnabled: false }, responses); + expect(userSyncs.length).to.equal(0); + }); - lastNextPageview = yieldbotLibStub.nextPageview.lastCall; - nextPageviewSlots = lastNextPageview.args[0]; - expect(nextPageviewSlots.medrec).to.deep.equal([['640', '480'], ['1024', '768'], ['300', '250']]); - expect(nextPageviewSlots.leaderboard).to.deep.equal([['640', '480'], ['1024', '768']]); + it('should return empty Array pixel option not provided', function() { + const userSyncs = YieldbotAdapter.getUserSyncs({ pixelNotHere: true }, responses); + expect(userSyncs.length).to.equal(0); + }); - let seventhCall = bidManagerStub.getCall(6); - let eighthCall = bidManagerStub.getCall(7); - let ninethCall = bidManagerStub.getCall(8); + it('should return image type pixels', function() { + const userSyncs = YieldbotAdapter.getUserSyncs({ pixelEnabled: true }, responses); + expect(userSyncs).to.eql( + [ + { type: 'image', url: 'https://usersync.dd9693a32aa1.com/00000000.gif?p=a' }, + { type: 'image', url: 'https://usersync.3b19503b37d8.com/00000000.gif?p=b' }, + { type: 'image', url: 'https://usersync.5cb389d36d30.com/00000000.gif?p=c' } + ] + ); + }); + }); - expect(seventhCall.args[0]).to.equal('/4294967296/adunit0'); - expect(seventhCall.args[1]).to.include(bidResponseNone); + describe('Adapter Auction Behavior', function() { + AdapterManager.bidderRegistry['yieldbot'] = newBidder(spec); + let sandbox, server, auctionManager; + const bidUrlRegexp = /yldbt\.com\/m\/1234\/v1\/init/; + beforeEach(function() { + sandbox = sinon.sandbox.create({ useFakeServer: true }); + server = sandbox.server; + server.respondImmediately = true; + server.respondWith( + 'GET', + bidUrlRegexp, + [ + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify(FIXTURE_SERVER_RESPONSE.body) + ] + ); + FIXTURE_SERVER_RESPONSE.user_syncs = []; + auctionManager = newAuctionManager(); + }); - expect(eighthCall.args[0]).to.equal('/4294967296/adunit1'); - expect(eighthCall.args[1]).to.include(bidResponseNone); + afterEach(function() { + auctionManager = null; + sandbox.restore(); + YieldbotAdapter._bidRequestCount = 0; + }); + + it('should provide auction bids', function(done) { + let bidCount = 0; + const firstAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + const bidResponseHandler = (event) => { + bidCount++; + if (bidCount === 4) { + events.off('bidResponse', bidResponseHandler); + done(); + } + }; + events.on('bidResponse', bidResponseHandler); + firstAuction.callBids(); + }); + + it('should provide multiple auctions with correct bid cpms', function(done) { + let bidCount = 0; + let firstAuctionId = ''; + let secondAuctionId = ''; + /* + * 'bidResponse' event handler checks for respective adUnit auctions and bids + */ + const bidResponseHandler = (event) => { + try { + switch (true) { + case event.adUnitCode === '/00000000/leaderboard' && event.auctionId === firstAuctionId: + expect(event.cpm, 'leaderboard, first auction cpm').to.equal(8); + break; + case event.adUnitCode === '/00000000/medrec' && event.auctionId === firstAuctionId: + expect(event.cpm, 'medrec, first auction cpm').to.equal(3); + break; + case event.adUnitCode === '/00000000/multi-size' && event.auctionId === firstAuctionId: + expect(event.cpm, 'multi-size, first auction cpm').to.equal(8); + break; + case event.adUnitCode === '/00000000/skyscraper' && event.auctionId === firstAuctionId: + expect(event.cpm, 'skyscraper, first auction cpm').to.equal(3); + break; + case event.adUnitCode === '/00000000/medrec' && event.auctionId === secondAuctionId: + expect(event.cpm, 'medrec, second auction cpm').to.equal(1.11); + break; + case event.adUnitCode === '/00000000/multi-size' && event.auctionId === secondAuctionId: + expect(event.cpm, 'multi-size, second auction cpm').to.equal(2.22); + break; + case event.adUnitCode === '/00000000/skyscraper' && event.auctionId === secondAuctionId: + expect(event.cpm, 'skyscraper, second auction cpm').to.equal(3.33); + break; + default: + done(new Error(`Bid response to assert not found: ${event.adUnitCode}:${event.size}:${event.auctionId}, [${firstAuctionId}, ${secondAuctionId}]`)); + } + bidCount++; + if (bidCount === 7) { + events.off('bidResponse', bidResponseHandler); + done(); + } + } catch (err) { + done(err); + } + }; + events.on('bidResponse', bidResponseHandler); + + /* + * First auction + */ + const firstAdUnits = FIXTURE_AD_UNITS; + const firstAdUnitCodes = FIXTURE_AD_UNITS.map(unit => unit.code); + const firstAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + firstAuctionId = firstAuction.getAuctionId(); + firstAuction.callBids(); + + /* + * Second auction with different bid values and fewer slots + */ + FIXTURE_AD_UNITS.shift(); + const FIXTURE_SERVER_RESPONSE_2 = utils.deepClone(FIXTURE_SERVER_RESPONSE); + FIXTURE_SERVER_RESPONSE_2.user_syncs = []; + FIXTURE_SERVER_RESPONSE_2.body.slots.shift(); + FIXTURE_SERVER_RESPONSE_2.body.slots.forEach((bid, idx) => { const num = idx + 1; bid.cpm = `${num}${num}${num}`; }); + const secondAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + secondAuctionId = secondAuction.getAuctionId(); + server.respondWith( + 'GET', + bidUrlRegexp, + [ + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify(FIXTURE_SERVER_RESPONSE_2.body) + ] + ); + secondAuction.callBids(); + }); + + it('should have refresh bid type after the first auction', function(done) { + const firstAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + firstAuction.callBids(); + + const secondAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + secondAuction.callBids(); + + const firstRequest = urlUtils.parse(server.firstRequest.url); + expect(firstRequest.search.bt, 'First request bid type').to.equal('init'); + + const secondRequest = urlUtils.parse(server.secondRequest.url); + expect(secondRequest.search.bt, 'Second request bid type').to.equal('refresh'); + + done(); + }); + + it('should use server response url_prefix property value after the first auction', function(done) { + const firstAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + firstAuction.callBids(); + + const secondAuction = auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ); + secondAuction.callBids(); + + expect(server.firstRequest.url, 'Default url prefix').to.match(/i\.yldbt\.com\/m\//); + expect(server.secondRequest.url, 'Locality url prefix').to.match(/ads-adseast-vpc\.yldbt\.com\/m\//); + + done(); + }); - expect(ninethCall.args[0]).to.equal('/4294967296/adunit2'); - expect(ninethCall.args[1]).to.include(bidResponseMedrec); + it('should increment the session page view depth only before the first auction', function(done) { + /* + * First visit: two bid requests + */ + for (let idx = 0; idx < 2; idx++) { + auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ).callBids(); + } + + const firstRequest = urlUtils.parse(server.firstRequest.url); + expect(firstRequest.search.pvd, 'First pvd').to.equal('1'); + + const secondRequest = urlUtils.parse(server.secondRequest.url); + expect(secondRequest.search.pvd, 'Second pvd').to.equal('1'); + + /* + * Next visit: two bid requests + */ + YieldbotAdapter._isInitialized = false; + YieldbotAdapter.initialize(); + for (let idx = 0; idx < 2; idx++) { + auctionManager.createAuction( + { + adUnits: FIXTURE_AD_UNITS, + adUnitCodes: FIXTURE_AD_UNITS.map(unit => unit.code) + } + ).callBids(); + } + + const nextVisitFirstRequest = urlUtils.parse(server.thirdRequest.url); + expect(nextVisitFirstRequest.search.pvd, 'Second visit, first pvd').to.equal('2'); + + const nextVisitSecondRequest = urlUtils.parse(server.lastRequest.url); + expect(nextVisitSecondRequest.search.pvd, 'Second visit, second pvd').to.equal('2'); + + done(); }); }); }); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js new file mode 100644 index 00000000000..1a5ca59d3a2 --- /dev/null +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -0,0 +1,112 @@ +import { expect } from 'chai' +import { spec } from 'modules/yieldlabBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' + +const REQUEST = { + 'bidder': 'yieldlab', + 'params': { + 'adslotId': '1111', + 'supplyId': '2222', + 'adSize': '728x90' + }, + 'bidderRequestId': '143346cf0f1731', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '2d925f27f5079f', + 'sizes': [728, 90] +} + +const RESPONSE = { + advertiser: 'yieldlab', + curl: 'https://www.yieldlab.de', + format: 0, + id: 1111, + price: 1, + pid: 2222 +} + +describe('yieldlabBidAdapter', () => { + 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', () => { + const request = { + 'params': { + 'adslotId': '1111', + 'supplyId': '2222', + 'adSize': '728x90' + } + } + expect(spec.isBidRequestValid(request)).to.equal(true) + }) + + it('should return false when required params are not passed', () => { + expect(spec.isBidRequestValid({})).to.equal(false) + }) + }) + + describe('buildRequests', () => { + const bidRequests = [REQUEST] + const request = spec.buildRequests(bidRequests) + + it('sends bid request to ENDPOINT via GET', () => { + expect(request.method).to.equal('GET') + }) + + it('returns a list of valid requests', () => { + expect(request.validBidRequests).to.eql([REQUEST]) + }) + }) + + describe('interpretResponse', () => { + const validRequests = { + validBidRequests: [REQUEST] + } + + it('handles nobid responses', () => { + expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0) + expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0) + }) + + it('should get correct bid response', () => { + const result = spec.interpretResponse({body: [RESPONSE]}, validRequests) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(0.01) + expect(result[0].width).to.equal(728) + expect(result[0].height).to.equal(90) + expect(result[0].creativeId).to.equal('1111') + expect(result[0].dealId).to.equal(2222) + expect(result[0].currency).to.equal('EUR') + expect(result[0].netRevenue).to.equal(false) + expect(result[0].ttl).to.equal(300) + expect(result[0].referrer).to.equal('') + expect(result[0].ad).to.include('<script src="https://ad.yieldlab.net/d/1111/2222/728x90?ts=') + }) + + it('should add vastUrl when type is video', () => { + const VIDEO_REQUEST = Object.assign({}, REQUEST, { + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }) + const validRequests = { + validBidRequests: [VIDEO_REQUEST] + } + const result = spec.interpretResponse({body: [RESPONSE]}, validRequests) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(0.01) + expect(result[0].mediaType).to.equal('video') + expect(result[0].vastUrl).to.include('https://ad.yieldlab.net/d/1111/2222/728x90?ts=') + }) + }) +}) diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 370aeb15457..8e4ac81c64f 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -1,209 +1,162 @@ -import {expect} from 'chai'; -import Adapter from '../../../modules/yieldmoBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import adLoader from '../../../src/adloader'; -import {parse as parseURL} from '../../../src/url'; - -describe('Yieldmo adapter', () => { - let bidsRequestedOriginal; - let adapter; - let sandbox; - - const bidderRequest = { - bidderCode: 'yieldmo', - bids: [ - { - bidId: 'bidId1', - bidder: 'yieldmo', - placementCode: 'foo', - sizes: [[728, 90]] - }, - { - bidId: 'bidId2', - bidder: 'yieldmo', - placementCode: 'bar', - sizes: [[300, 600], [300, 250]] - } - ] +import { expect } from 'chai'; +import { spec } from 'modules/yieldmoBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; + +describe('YieldmoAdapter', () => { + const adapter = newBidder(spec); + const ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; + + let bid = { + bidder: 'yieldmo', + params: {}, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' }; + let bidArray = [bid]; - beforeEach(() => { - bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested; - $$PREBID_GLOBAL$$._bidsRequested = []; - - adapter = new Adapter(); - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); - - $$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal; - }); - - describe('callBids', () => { - let bidRequestURL; - - beforeEach(() => { - sandbox.stub(adLoader, 'loadScript'); - adapter.callBids(bidderRequest); - - bidRequestURL = adLoader.loadScript.firstCall.args[0]; + describe('isBidRequestValid', () => { + it('should return true when necessary information is found', () => { + expect(spec.isBidRequestValid(bid)).to.be.true; }); - it('should load a script with passed bid params', () => { - let route = 'http://bid.yieldmo.com/exchange/prebid?'; - let requestParams = parseURL(bidRequestURL).search; - let parsedPlacementParams = JSON.parse(decodeURIComponent(requestParams.p)); - - sinon.assert.calledOnce(adLoader.loadScript); - expect(bidRequestURL).to.contain(route); - - // placement 1 - expect(parsedPlacementParams[0]).to.have.property('callback_id', 'bidId1'); - expect(parsedPlacementParams[0]).to.have.property('placement_id', 'foo'); - expect(parsedPlacementParams[0].sizes[0][0]).to.equal(728); - expect(parsedPlacementParams[0].sizes[0][1]).to.equal(90); - - // placement 2 - expect(parsedPlacementParams[1]).to.have.property('callback_id', 'bidId2'); - expect(parsedPlacementParams[1]).to.have.property('placement_id', 'bar'); - expect(parsedPlacementParams[1].sizes[0][0]).to.equal(300); - expect(parsedPlacementParams[1].sizes[0][1]).to.equal(600); - expect(parsedPlacementParams[1].sizes[1][0]).to.equal(300); - expect(parsedPlacementParams[1].sizes[1][1]).to.equal(250); - - // impression information - expect(requestParams).to.have.property('callback', '$$PREBID_GLOBAL$$.YMCB'); - expect(requestParams).to.have.property('page_url'); - }); - }); + it('should return false when necessary information is not found', () => { + // empty bid + expect(spec.isBidRequestValid({})).to.be.false; - describe('YMCB', () => { - it('should exist and be a function', () => { - expect($$PREBID_GLOBAL$$.YMCB).to.exist.and.to.be.a('function'); - }); - }); + // empty bidId + bid.bidId = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; - describe('add bids to the manager', () => { - let firstBid; - let secondBid; + // empty adUnitCode + bid.bidId = '30b31c1838de1e'; + bid.adUnitCode = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; - beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - - // respond - let bidderReponse = [{ - 'cpm': 3.45455, - 'width': 300, - 'height': 250, - 'callback_id': 'bidId1', - 'ad': '<html><head></head><body>HELLO YIELDMO AD</body></html>' - }, { - 'cpm': 4.35455, - 'width': 400, - 'height': 350, - 'callback_id': 'bidId2', - 'ad': '<html><head></head><body>HELLO YIELDMO AD</body></html>' - }]; - - $$PREBID_GLOBAL$$.YMCB(bidderReponse); - - firstBid = bidManager.addBidResponse.firstCall.args[1]; - secondBid = bidManager.addBidResponse.secondCall.args[1]; - }); - - it('should add a bid object for each bid', () => { - sinon.assert.calledTwice(bidManager.addBidResponse); - }); - - it('should pass the correct placement code as first param', () => { - let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; - let secondPlacementCode = bidManager.addBidResponse.secondCall.args[0]; - - expect(firstPlacementCode).to.eql('foo'); - expect(secondPlacementCode).to.eql('bar'); - }); - - it('should have a good statusCode', () => { - expect(firstBid.getStatusCode()).to.eql(1); - expect(secondBid.getStatusCode()).to.eql(1); - }); - - it('should add the CPM to the bid object', () => { - expect(firstBid).to.have.property('cpm', 3.45455); - expect(secondBid).to.have.property('cpm', 4.35455); + bid.adUnitCode = 'adunit-code'; }); + }); - it('should add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'yieldmo'); - expect(secondBid).to.have.property('bidderCode', 'yieldmo'); + describe('buildRequests', () => { + it('should attempt to send bid requests to the endpoint via GET', () => { + const request = spec.buildRequests(bidArray); + expect(request.method).to.equal('GET'); + expect(request.url).to.be.equal(ENDPOINT); }); - it('should include the ad on the bid object', () => { - expect(firstBid).to.have.property('ad'); - expect(secondBid).to.have.property('ad'); - }); + it('should place bid information into the p parameter of data', () => { + let placementInfo = spec.buildRequests(bidArray).data.p; + expect(placementInfo).to.equal('[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]]}]'); - it('should include the size on the bid object', () => { - expect(firstBid).to.have.property('width', 300); - expect(firstBid).to.have.property('height', 250); - expect(secondBid).to.have.property('width', 400); - expect(secondBid).to.have.property('height', 350); - }); + bidArray.push({ + bidder: 'yieldmo', + params: {}, + adUnitCode: 'adunit-code-1', + sizes: [[300, 250], [300, 600]], + bidId: '123456789', + bidderRequestId: '987654321', + auctionId: '0246810' + }); + + // multiple placements + placementInfo = spec.buildRequests(bidArray).data.p; + expect(placementInfo).to.equal('[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]]},{"placement_id":"adunit-code-1","callback_id":"123456789","sizes":[[300,250],[300,600]]}]'); + }); + + it('should add placement id if given', () => { + bidArray[0].params.placementId = 'ym_1293871298'; + let placementInfo = spec.buildRequests(bidArray).data.p; + expect(placementInfo).to.include('"ym_placement_id":"ym_1293871298"}'); + expect(placementInfo).not.to.include('"ym_placement_id":"ym_0987654321"}'); + + bidArray[1].params.placementId = 'ym_0987654321'; + placementInfo = spec.buildRequests(bidArray).data.p; + expect(placementInfo).to.include('"ym_placement_id":"ym_1293871298"}'); + expect(placementInfo).to.include('"ym_placement_id":"ym_0987654321"}'); + }); + + it('should add additional information to data parameter of request', () => { + const data = spec.buildRequests(bidArray).data; + expect(data.hasOwnProperty('page_url')).to.be.true; + expect(data.hasOwnProperty('bust')).to.be.true; + expect(data.hasOwnProperty('pr')).to.be.true; + expect(data.hasOwnProperty('scrd')).to.be.true; + expect(data.dnt).to.be.false; + expect(data.e).to.equal(90); + expect(data.hasOwnProperty('description')).to.be.true; + expect(data.hasOwnProperty('title')).to.be.true; + expect(data.hasOwnProperty('h')).to.be.true; + expect(data.hasOwnProperty('w')).to.be.true; + }) }); - describe('add empty bids if no bid returned', () => { - let firstBid; - let secondBid; + describe('interpretResponse', () => { + let serverResponse; beforeEach(() => { - sandbox.stub(bidManager, 'addBidResponse'); - - $$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest); - - // respond - let bidderReponse = [{ - 'status': 'no_bid', - 'callback_id': 'bidId1' - }, { - 'status': 'no_bid', - 'callback_id': 'bidId2' - }]; - - $$PREBID_GLOBAL$$.YMCB(bidderReponse); - - firstBid = bidManager.addBidResponse.firstCall.args[1]; - secondBid = bidManager.addBidResponse.secondCall.args[1]; - }); - - it('should add a bid object for each bid', () => { - sinon.assert.calledTwice(bidManager.addBidResponse); - }); - - it('should include the bid request bidId as the adId', () => { - expect(firstBid).to.have.property('adId', 'bidId1'); - expect(secondBid).to.have.property('adId', 'bidId2'); - }); - - it('should have an error statusCode', () => { - expect(firstBid.getStatusCode()).to.eql(2); - expect(secondBid.getStatusCode()).to.eql(2); - }); - - it('should pass the correct placement code as first param', () => { - let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; - let secondPlacementCode = bidManager.addBidResponse.secondCall.args[0]; - - expect(firstPlacementCode).to.eql('foo'); - expect(secondPlacementCode).to.eql('bar'); + serverResponse = { + body: [{ + callback_id: '21989fdbef550a', + cpm: 3.45455, + width: 300, + height: 250, + ad: '<html><head></head><body><script>//GEX ad object</script><div id=\"ym_123\" class=\"ym\"></div><script>//js code</script></body></html>', + creativeId: '9874652394875' + }], + header: 'header?' + }; + }) + + it('should correctly reorder the server response', () => { + const newResponse = spec.interpretResponse(serverResponse); + expect(newResponse.length).to.be.equal(1); + expect(newResponse[0]).to.deep.equal({ + requestId: '21989fdbef550a', + cpm: 3.45455, + width: 300, + height: 250, + creativeId: '9874652394875', + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: '<html><head></head><body><script>//GEX ad object</script><div id=\"ym_123\" class=\"ym\"></div><script>//js code</script></body></html>' + }); + }); + + it('should not add responses if the cpm is 0 or null', () => { + serverResponse.body[0].cpm = 0; + let response = spec.interpretResponse(serverResponse); + expect(response).to.deep.equal([]); + + serverResponse.body[0].cpm = null; + response = spec.interpretResponse(serverResponse); + expect(response).to.deep.equal([]) }); + }); - it('should add the bidder code to the bid object', () => { - expect(firstBid).to.have.property('bidderCode', 'yieldmo'); - expect(secondBid).to.have.property('bidderCode', 'yieldmo'); + describe('getUserSync', () => { + const SYNC_ENDPOINT = 'https://static.yieldmo.com/blank.min.html?orig='; + let options = { + iframeEnabled: true, + pixelEnabled: true + }; + + it('should return a tracker with type and url as parameters', () => { + if (/iPhone|iPad|iPod/i.test(window.navigator.userAgent)) { + expect(spec.getUserSync(options)).to.deep.equal([{ + type: 'iframe', + url: SYNC_ENDPOINT + utils.getOrigin() + }]); + + options.iframeEnabled = false; + expect(spec.getUserSync(options)).to.deep.equal([]); + } else { + // not ios, so tracker will fail + expect(spec.getUserSync(options)).to.deep.equal([]); + } }); }); }); diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js new file mode 100644 index 00000000000..e763257f097 --- /dev/null +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -0,0 +1,147 @@ +import { expect } from 'chai'; +import { spec } from 'modules/yieldoneBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const ENDPOINT = '//y.one.impact-ad.jp/h_bid'; + +describe('yieldoneBidAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'yieldone', + 'params': { + placementId: '44082' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when placementId not passed correctly', () => { + bid.params.placementId = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', () => { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'yieldone', + 'params': { + placementId: '44082' + }, + 'adUnitCode': 'adunit-code1', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }, + { + 'bidder': 'yieldone', + 'params': { + placementId: '44337' + }, + '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 GET', () => { + expect(request[0].method).to.equal('GET'); + expect(request[1].method).to.equal('GET'); + }); + + it('attaches source and version to endpoint URL as query params', () => { + expect(request[0].url).to.equal(ENDPOINT); + expect(request[1].url).to.equal(ENDPOINT); + }); + }); + + describe('interpretResponse', () => { + let bidRequest = [ + { + 'method': 'GET', + 'url': '//y.one.impact-ad.jp/h_bid', + 'data': { + 'v': 'hb1', + 'p': '44082', + 'w': '300', + 'h': '250', + 'cb': 12892917383, + 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', + 'uid': '23beaa6af6cdde', + 't': 'i' + } + } + ]; + + let serverResponse = { + body: { + 'adTag': '<!-- adtag -->', + 'cpm': 0.0536616, + 'crid': '2494768', + 'statusMessage': 'Bid available', + 'uid': '23beaa6af6cdde', + 'width': 300, + 'height': 250 + } + }; + + it('should get the correct bid response', () => { + let expectedResponse = [{ + 'requestId': '23beaa6af6cdde', + 'cpm': 53.6616, + 'width': 300, + 'height': 250, + 'creativeId': '2494768', + 'dealId': '', + 'currency': 'JPY', + 'netRevenue': true, + 'ttl': 3000, + 'referrer': '', + 'ad': '<!-- adtag -->' + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + + it('handles empty bid response', () => { + 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/yuktamediaAnalyticsAdaptor_spec.js b/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js new file mode 100644 index 00000000000..29d23d66bd2 --- /dev/null +++ b/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js @@ -0,0 +1,177 @@ +import yuktamediaAnalyticsAdapter from 'modules/yuktamediaAnalyticsAdapter'; +import { expect } from 'chai'; +let adaptermanager = require('src/adaptermanager'); +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('YuktaMedia analytics adapter', () => { + let xhr; + let requests; + + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(() => { + xhr.restore(); + events.getEvents.restore(); + }); + + describe('track', () => { + let initOptions = { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==' + }; + + adaptermanager.registerAnalyticsAdapter({ + code: 'yuktamedia', + adapter: yuktamediaAnalyticsAdapter + }); + + beforeEach(() => { + adaptermanager.enableAnalytics({ + provider: 'yuktamedia', + options: initOptions + }); + }); + + afterEach(() => { + yuktamediaAnalyticsAdapter.disableAnalytics(); + }); + + it('builds and sends auction data', () => { + let auctionTimestamp = 1496510254313; + let bidRequest = { + 'bidderCode': 'appnexus', + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + 'bidderRequestId': '173209942f8bdd', + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394' + }, + 'crumbs': { + 'pubcid': '9a2a4e71-f39b-405f-aecc-19efc22b618d' + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': '2f481ff1-8d20-4c28-8e36-e384e9e3eec6', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '2eddfdc0c791dc', + 'bidderRequestId': '173209942f8bdd', + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + } + ], + 'auctionStart': 1522265863591, + 'timeout': 3000, + 'start': 1522265863600, + 'doneCbCallCount': 1 + }; + let 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' + }; + let bidTimeoutArgsV1 = [ + { + 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' + } + ]; + let bid = { + 'bidderCode': 'appnexus', + 'bidId': '2eddfdc0c791dc', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'requestId': '173209942f8bdd', + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + 'renderStatus': 2, + 'cpm': 0.5, + 'creativeId': 29681110, + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': true, + 'requestTimestamp': 1522265863600, + 'responseTimestamp': 1522265866110, + 'sizes': '300x250,300x600', + 'statusMessage': 'Bid available', + 'timeToRespond': 2510 + } + + // Step 1: Send auction init event + events.emit(constants.EVENTS.AUCTION_INIT, { + timestamp: auctionTimestamp + }); + + // Step 2: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + + // Step 3: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + // Step 4: Send bid time out event + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); + + // Step 5: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, {}, 'auctionEnd'); + + expect(requests.length).to.equal(1); + + let auctionEventData = JSON.parse(requests[0].requestBody); + + expect(auctionEventData.bids.length).to.equal(1); + expect(auctionEventData.bids[0]).to.deep.equal(bid); + + expect(auctionEventData.initOptions).to.deep.equal(initOptions); + + // Step 6: Send auction bid won event + events.emit(constants.EVENTS.BID_WON, { + 'bidderCode': 'appnexus', + 'statusMessage': 'Bid available', + 'adId': '108abedd106b669', + 'auctionId': '6355d610-7cdc-4009-a866-f91997fd24bb', + 'responseTimestamp': 1522144433058, + 'requestTimestamp': 1522144432923, + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'timeToRespond': 135, + 'size': '300x250', + 'status': 'rendered' + }, 'won'); + + expect(requests.length).to.equal(2); + + let winEventData = JSON.parse(requests[1].requestBody); + + expect(winEventData.bidWon.status).to.equal('rendered'); + expect(winEventData.initOptions).to.deep.equal(initOptions); + }); + }); +}); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 977575a4d19..653f858576f 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { fireNativeTrackers, getNativeTargeting } from 'src/native'; +import { fireNativeTrackers, getNativeTargeting, nativeBidIsValid } from 'src/native'; const utils = require('src/utils'); const bid = { @@ -11,18 +11,22 @@ const bid = { clickUrl: 'https://www.link.example', clickTrackers: ['https://tracker.example'], impressionTrackers: ['https://impression.example'], + javascriptTrackers: '<script src=\"http://www.foobar.js\"></script>' } }; describe('native.js', () => { let triggerPixelStub; + let insertHtmlIntoIframeStub; beforeEach(() => { triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + insertHtmlIntoIframeStub = sinon.stub(utils, 'insertHtmlIntoIframe'); }); afterEach(() => { utils.triggerPixel.restore(); + utils.insertHtmlIntoIframe.restore(); }); it('gets native targeting keys', () => { @@ -36,6 +40,7 @@ describe('native.js', () => { fireNativeTrackers({}, bid); sinon.assert.calledOnce(triggerPixelStub); sinon.assert.calledWith(triggerPixelStub, bid.native.impressionTrackers[0]); + sinon.assert.calledWith(insertHtmlIntoIframeStub, bid.native.javascriptTrackers); }); it('fires click trackers', () => { @@ -44,3 +49,116 @@ describe('native.js', () => { sinon.assert.calledWith(triggerPixelStub, bid.native.clickTrackers[0]); }); }); + +describe('validate native', () => { + let bidReq = [{ + bids: [{ + bidderCode: 'test_bidder', + bidId: 'test_bid_id', + mediaTypes: { + native: { + title: { + required: true, + }, + body: { + required: true, + }, + image: { + required: true, + sizes: [150, 50], + aspect_ratios: [150, 50] + }, + icon: { + required: true, + sizes: [50, 50] + }, + } + } + }] + }]; + + let validBid = { + adId: 'test_bid_id', + adUnitCode: '123/prebid_native_adunit', + bidder: 'test_bidder', + native: { + body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + clickTrackers: ['http://my.click.tracker/url'], + icon: { + url: 'http://my.image.file/ad_image.jpg', + height: 75, + width: 75 + }, + image: { + url: 'http://my.icon.file/ad_icon.jpg', + height: 2250, + width: 3000 + }, + clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', + impressionTrackers: ['http://my.imp.tracker/url'], + javascriptTrackers: '<script src=\"http://www.foobar.js\"></script>', + title: 'This is an example Prebid Native creative' + } + }; + + let noIconDimBid = { + adId: 'test_bid_id', + adUnitCode: '123/prebid_native_adunit', + bidder: 'test_bidder', + native: { + body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + clickTrackers: ['http://my.click.tracker/url'], + icon: { + url: 'http://my.image.file/ad_image.jpg', + height: 0, + width: 0 + }, + image: { + url: 'http://my.icon.file/ad_icon.jpg', + height: 2250, + width: 3000 + }, + clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', + impressionTrackers: ['http://my.imp.tracker/url'], + javascriptTrackers: '<script src=\"http://www.foobar.js\"></script>', + title: 'This is an example Prebid Native creative' + } + }; + + let noImgDimBid = { + adId: 'test_bid_id', + adUnitCode: '123/prebid_native_adunit', + bidder: 'test_bidder', + native: { + body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + clickTrackers: ['http://my.click.tracker/url'], + icon: { + url: 'http://my.image.file/ad_image.jpg', + height: 75, + width: 75 + }, + image: { + url: 'http://my.icon.file/ad_icon.jpg', + height: 0, + width: 0 + }, + clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', + impressionTrackers: ['http://my.imp.tracker/url'], + javascriptTrackers: '<script src=\"http://www.foobar.js\"></script>', + title: 'This is an example Prebid Native creative' + } + }; + + beforeEach(() => {}); + + afterEach(() => {}); + + it('should reject bid if no image sizes are defined', () => { + let result = nativeBidIsValid(validBid, bidReq); + expect(result).to.be.true; + result = nativeBidIsValid(noIconDimBid, bidReq); + expect(result).to.be.false; + result = nativeBidIsValid(noImgDimBid, bidReq); + expect(result).to.be.false; + }); +}); diff --git a/test/spec/renderer_spec.js b/test/spec/renderer_spec.js index 282c4841ac0..a511a1b214c 100644 --- a/test/spec/renderer_spec.js +++ b/test/spec/renderer_spec.js @@ -1,20 +1,38 @@ import { expect } from 'chai'; import { Renderer } from 'src/Renderer'; +const adloader = require('../../src/adloader'); describe('Renderer: A renderer installed on a bid response', () => { - const testRenderer1 = Renderer.install({ - url: 'https://httpbin.org/post', - config: { test: 'config1' }, - id: 1 - }); - const testRenderer2 = Renderer.install({ - url: 'https://httpbin.org/post', - config: { test: 'config2' }, - id: 2 + let testRenderer1; + let testRenderer2; + let spyRenderFn; + let spyEventHandler; + + let loadScriptStub; + + beforeEach(() => { + loadScriptStub = sinon.stub(adloader, 'loadScript').callsFake((...args) => { + args[1](); + }); + + 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(); }); - const spyRenderFn = sinon.spy(); - const spyEventHandler = sinon.spy(); + afterEach(() => { + loadScriptStub.restore(); + }); it('is an instance of Renderer', () => { expect(testRenderer1 instanceof Renderer).to.equal(true); @@ -34,12 +52,11 @@ describe('Renderer: A renderer installed on a bid response', () => { it('sets a render function with setRender method', () => { testRenderer1.setRender(spyRenderFn); expect(typeof testRenderer1.render).to.equal('function'); - testRenderer1.render(); expect(spyRenderFn.called).to.equal(true); }); - it('sets event handlers with setEventHandlers method', () => { + it('sets event handlers with setEventHandlers method and handles events with installed handlers', () => { testRenderer1.setEventHandlers({ testEvent: spyEventHandler }); @@ -47,16 +64,14 @@ describe('Renderer: A renderer installed on a bid response', () => { expect(testRenderer1.handlers).to.deep.equal({ testEvent: spyEventHandler }); - }); - it('handles events with installed handlers', () => { testRenderer1.handleVideoEvent({ id: 1, eventName: 'testEvent' }); expect(spyEventHandler.called).to.equal(true); }); it('pushes commands to queue if renderer is not loaded', () => { + testRenderer1.loaded = false; testRenderer1.push(spyRenderFn); - expect(testRenderer1.cmd.length).to.equal(1); // clear queue for next tests @@ -70,6 +85,7 @@ describe('Renderer: A renderer installed on a bid response', () => { testRenderer1.push(func); expect(testRenderer1.cmd.length).to.equal(0); + sinon.assert.calledOnce(func); }); diff --git a/test/spec/sizeMapping_spec.js b/test/spec/sizeMapping_spec.js index 471d3ba2752..74b86a8c5aa 100644 --- a/test/spec/sizeMapping_spec.js +++ b/test/spec/sizeMapping_spec.js @@ -1,123 +1,209 @@ import { expect } from 'chai'; -import * as sizeMapping from 'src/sizeMapping'; - -var validAdUnit = { - 'sizes': [300, 250], - 'sizeMapping': [ - { - 'minWidth': 1024, - 'sizes': [[300, 250], [728, 90]] - }, - { - 'minWidth': 480, - 'sizes': [120, 60] - }, - { - 'minWidth': 0, - 'sizes': [20, 20] - } - ] -}; - -var invalidAdUnit = { - 'sizes': [300, 250], - 'sizeMapping': {} // wrong type -}; - -var invalidAdUnit2 = { - 'sizes': [300, 250], - 'sizeMapping': [{ - foo: 'bar' // bad - }] -}; - -let mockWindow = {}; - -function resetMockWindow() { - mockWindow = { - document: { - body: { - clientWidth: 1024 - }, - documentElement: { - clientWidth: 1024 +import { resolveStatus, setSizeConfig } from 'src/sizeMapping'; +import includes from 'core-js/library/fn/array/includes'; + +let utils = require('src/utils'); +let deepClone = utils.deepClone; + +describe('sizeMapping', () => { + var testSizes = [[970, 90], [728, 90], [300, 250], [300, 100], [80, 80]]; + + var sizeConfig = [{ + 'mediaQuery': '(min-width: 1200px)', + 'sizesSupported': [ + [970, 90], + [728, 90], + [300, 250] + ] + }, { + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': [ + [728, 90], + [300, 250], + [300, 100] + ] + }, { + 'mediaQuery': '(min-width: 0px) and (max-width: 767px)', + 'sizesSupported': [] + }]; + + var sizeConfigWithLabels = [{ + 'mediaQuery': '(min-width: 1200px)', + 'labels': ['desktop'] + }, { + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': [ + [728, 90], + [300, 250] + ], + 'labels': ['tablet', 'phone'] + }, { + 'mediaQuery': '(min-width: 0px) and (max-width: 767px)', + 'sizesSupported': [ + [300, 250], + [300, 100] + ], + 'labels': ['phone'] + }]; + + let sandbox, + matchMediaOverride; + + beforeEach(() => { + setSizeConfig(sizeConfig); + + sandbox = sinon.sandbox.create(); + + matchMediaOverride = {matches: false}; + + sandbox.stub(window, 'matchMedia').callsFake((...args) => { + if (typeof matchMediaOverride === 'function') { + return matchMediaOverride.apply(window, args); } - }, - innerWidth: 1024 - }; -} - -describe('sizeMapping', function() { - beforeEach(resetMockWindow); - - it('minWidth should be inclusive', function() { - mockWindow.innerWidth = 1024; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(validAdUnit); - expect(sizes).to.deep.equal([[300, 250], [728, 90]]); + return matchMediaOverride; + }); }); - it('mapSizes 1029 width', function() { - mockWindow.innerWidth = 1029; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(validAdUnit); - expect(sizes).to.deep.equal([[300, 250], [728, 90]]); - expect(validAdUnit.sizes).to.deep.equal([300, 250]); - }); + afterEach(() => { + setSizeConfig([]); - it('mapSizes 400 width', function() { - mockWindow.innerWidth = 400; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(validAdUnit); - expect(sizes).to.deep.equal([20, 20]); - expect(validAdUnit.sizes).to.deep.equal([300, 250]); + sandbox.restore(); }); - it('mapSizes - invalid adUnit - should return sizes', function() { - mockWindow.innerWidth = 1029; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(invalidAdUnit); - expect(sizes).to.deep.equal([300, 250]); - expect(invalidAdUnit.sizes).to.deep.equal([300, 250]); - - mockWindow.innerWidth = 400; - sizeMapping.setWindow(mockWindow); - sizes = sizeMapping.mapSizes(invalidAdUnit); - expect(sizes).to.deep.equal([300, 250]); - expect(invalidAdUnit.sizes).to.deep.equal([300, 250]); - }); + describe('when handling sizes', () => { + it('should log a warning when mediaQuery property missing from sizeConfig', () => { + let errorConfig = deepClone(sizeConfig); - it('mapSizes - should return desktop (largest) sizes if screen width not detected', function() { - mockWindow.innerWidth = 0; - mockWindow.document.body.clientWidth = 0; - mockWindow.document.documentElement.clientWidth = 0; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(validAdUnit); - expect(sizes).to.deep.equal([[300, 250], [728, 90]]); - expect(validAdUnit.sizes).to.deep.equal([300, 250]); - }); + delete errorConfig[0].mediaQuery; - it('mapSizes - should return sizes if sizemapping improperly defined ', function() { - mockWindow.innerWidth = 0; - mockWindow.document.body.clientWidth = 0; - mockWindow.document.documentElement.clientWidth = 0; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(invalidAdUnit2); - expect(sizes).to.deep.equal([300, 250]); - expect(validAdUnit.sizes).to.deep.equal([300, 250]); - }); + sandbox.stub(utils, 'logWarn'); + + resolveStatus(undefined, testSizes, errorConfig); + expect(utils.logWarn.firstCall.args[0]).to.match(/missing.+?mediaQuery/); + }); + + it('when one mediaQuery block matches, it should filter the adUnit.sizes passed in', () => { + matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; + + let status = resolveStatus(undefined, testSizes, sizeConfig); + + expect(status).to.deep.equal({ + active: true, + sizes: [[970, 90], [728, 90], [300, 250]] + }) + }); + + it('when multiple mediaQuery block matches, it should filter a union of the matched sizesSupported', () => { + matchMediaOverride = (str) => includes([ + '(min-width: 1200px)', + '(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]] + }) + }); - it('getScreenWidth', function() { - mockWindow.innerWidth = 900; - mockWindow.document.body.clientWidth = 900; - mockWindow.document.documentElement.clientWidth = 900; - expect(sizeMapping.getScreenWidth(mockWindow)).to.equal(900); + it('if no mediaQueries match, it should allow all sizes specified', () => { + matchMediaOverride = () => ({matches: false}); + + let status = resolveStatus(undefined, testSizes, sizeConfig); + expect(status).to.deep.equal({ + active: true, + sizes: testSizes + }) + }); + + it('if a mediaQuery matches and has sizesSupported: [], it should filter all sizes', () => { + 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: [] + }) + }); + + it('if a mediaQuery matches and no sizesSupported specified, it should not effect adUnit.sizes', () => { + 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 + }) + }); }); - it('getScreenWidth - should return 0 if it cannot deteremine size', function() { - mockWindow.innerWidth = null; - mockWindow.document.body.clientWidth = null; - mockWindow.document.documentElement.clientWidth = null; - expect(sizeMapping.getScreenWidth(mockWindow)).to.equal(0); + describe('when handling labels', () => { + it('should activate/deactivate adUnits/bidders based on sizeConfig.labels', () => { + matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; + + let status = resolveStatus({ + labels: ['desktop'] + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: true, + sizes: testSizes + }); + + status = resolveStatus({ + labels: ['tablet'] + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: false, + sizes: testSizes + }); + }); + + it('should active/deactivate adUnits/bidders based on requestBids labels', () => { + let activeLabels = ['us-visitor', 'desktop', 'smart']; + + let status = resolveStatus({ + labels: ['uk-visitor'], + activeLabels + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: false, + sizes: testSizes + }); + + status = resolveStatus({ + labels: ['us-visitor'], + activeLabels + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: true, + sizes: testSizes + }); + + status = resolveStatus({ + labels: ['us-visitor', 'tablet'], + labelAll: true, + activeLabels + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: false, + sizes: testSizes + }); + + status = resolveStatus({ + labels: ['us-visitor', 'desktop'], + labelAll: true, + activeLabels + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: true, + sizes: testSizes + }); + }); }); }); diff --git a/test/spec/unit/bidmanager_spec.js b/test/spec/unit/bidmanager_spec.js deleted file mode 100644 index 290bf06733e..00000000000 --- a/test/spec/unit/bidmanager_spec.js +++ /dev/null @@ -1,259 +0,0 @@ -import { expect } from 'chai'; -import { config } from 'src/config'; -import constants from 'src/constants'; -import events from 'src/events'; - -import * as bidManager from 'src/bidmanager'; -import useVideoCacheStubs from 'test/mocks/videoCacheStub'; -import adUnit from 'test/fixtures/video/adUnit'; -import bidRequest from 'test/fixtures/video/bidRequest'; -import urlBidResponse from 'test/fixtures/video/vastUrlResponse'; -import payloadBidResponse from 'test/fixtures/video/vastPayloadResponse'; - -function adjustCpm(cpm) { - return cpm + 1; -} - -describe('The Bid Manager', () => { - before(() => { - $$PREBID_GLOBAL$$.cbTimeout = 5000; - $$PREBID_GLOBAL$$.timeoutBuffer = 50; - }); - - describe('addBidResponse() function,', () => { - /** - * Add the bidResponse fixture as a bid into the auction, and run some assertions - * to verify: - * - * 1. Whether or not that bid got added. - * 2. Whether or not the "end of auction" callbacks got called. - */ - function testAddVideoBid(expectBidAdded, expectCallbackCalled, videoCacheStubProvider, usePayloadResponse) { - return function() { - const usePrebidCache = config.getConfig('usePrebidCache'); - const bidToUse = usePayloadResponse ? payloadBidResponse : urlBidResponse; - const mockResponse = Object.assign({}, bidToUse); - const callback = sinon.spy(); - bidManager.addOneTimeCallback(callback); - - mockResponse.getSize = function() { - return `${this.height}x${this.width}`; - }; - bidManager.addBidResponse(adUnit.code, mockResponse); - - const expectedBidsReceived = expectBidAdded ? 1 : 0; - expect($$PREBID_GLOBAL$$._bidsReceived.length).to.equal(expectedBidsReceived); - - const storeStub = videoCacheStubProvider().store; - if (usePrebidCache) { - expect(storeStub.calledOnce).to.equal(true); - expect(storeStub.getCall(0).args[0][0]).to.equal(mockResponse); - } else { - expect(storeStub.called).to.equal(false); - } - - if (expectedBidsReceived === 1) { - const bid = $$PREBID_GLOBAL$$._bidsReceived[0]; - - // Ensures that the BidAdjustment listeners execute before the bid goes into the auction. - expect(bid.cpm).to.equal(adjustCpm(0.1)); - - if (usePayloadResponse) { - expect(bid.vastXml).to.equal('<VAST version="3.0"></VAST>'); - if (usePrebidCache) { - expect(bid.vastUrl).to.equal(`https://prebid.adnxs.com/pbc/v1/cache?uuid=FAKE_UUID`); - } - } else { - expect(bid.vastUrl).to.equal('www.myVastUrl.com'); - } - if (usePrebidCache) { - expect(bid.videoCacheKey).to.equal('FAKE_UUID'); - } - } - if (expectCallbackCalled) { - expect(callback.calledOnce).to.equal(true); - } else { - expect(callback.called).to.equal(false); - } - }; - } - - /** - * Initialize the global state so that the auction-space looks like we want it to. - * - * @param {Array} adUnits The array of ad units which should appear in this auction. - * @param {function} bidRequestTweaker A function which accepts a basic bidRequest, and - * transforms it to prepare it for auction. - */ - function prepAuction(adUnits, bidRequestTweaker) { - function bidAdjuster(bid) { - if (bid.hasOwnProperty('cpm')) { - bid.hadCpmDuringBidAdjustment = true; - } - if (bid.hasOwnProperty('adUnitCode')) { - bid.hadAdUnitCodeDuringBidAdjustment = true; - } - if (bid.hasOwnProperty('timeToRespond')) { - bid.hadTimeToRespondDuringBidAdjustment = true; - } - if (bid.hasOwnProperty('requestTimestamp')) { - bid.hadRequestTimestampDuringBidAdjustment = true; - } - if (bid.hasOwnProperty('responseTimestamp')) { - bid.hadResponseTimestampDuringBidAdjustment = true; - } - bid.cpm = adjustCpm(bid.cpm); - } - beforeEach(() => { - let thisBidRequest = bidRequest; - if (bidRequestTweaker) { - thisBidRequest = JSON.parse(JSON.stringify(bidRequest)); - bidRequestTweaker(thisBidRequest); - } - - events.on(constants.EVENTS.BID_ADJUSTMENT, bidAdjuster); - - $$PREBID_GLOBAL$$.adUnits = adUnits; - $$PREBID_GLOBAL$$._bidsRequested = [thisBidRequest]; - $$PREBID_GLOBAL$$._bidsReceived = []; - $$PREBID_GLOBAL$$._adUnitCodes = $$PREBID_GLOBAL$$.adUnits.map(unit => unit.code); - }); - - afterEach(() => { - events.off(constants.EVENTS.BID_ADJUSTMENT, bidAdjuster); - }); - } - - function auctionStart(timedOut) { - return timedOut - ? new Date().getTime() - $$PREBID_GLOBAL$$.cbTimeout - $$PREBID_GLOBAL$$.timeoutBuffer - 1 - : new Date().getTime(); - } - - function testAddExpectMoreBids(stubProvider) { - return () => { - // Set up the global state so that we expect two bids, and the auction started just now - // (so as to reduce the chance of timeout. This assumes that the unit test runs run in < 5000 ms). - prepAuction( - [adUnit, Object.assign({}, adUnit, { code: 'video2' })], - (bidRequest) => { - const tweakedBidRequestBid = Object.assign({}, bidRequest.bids[0], { placementCode: 'video2' }); - bidRequest.bids.push(tweakedBidRequestBid); - bidRequest.start = auctionStart(false); - }); - - it("should add video bids, but shouldn't call the end-of-auction callbacks yet", - testAddVideoBid(true, false, stubProvider, false)); - }; - } - - describe('when prebid-cache is enabled', () => { - before(() => { - config.setConfig({ - usePrebidCache: true, - }); - }); - - describe('when the cache is functioning properly', () => { - let stubProvider = useVideoCacheStubs({ - store: [{ uuid: 'FAKE_UUID' }], - }); - - describe('when more bids are expected after this one', testAddExpectMoreBids(stubProvider)); - - describe('when this is the last bid expected in the auction', () => { - // Set up the global state so that we expect only one bid, and the auction started just now - // (so as to reduce the chance of timeout. This assumes that the unit test runs run in < 5000 ms). - prepAuction([adUnit], (bidRequest) => bidRequest.start = auctionStart(false)); - - it("shouldn't add invalid bids", () => { - bidManager.addBidResponse('', { }); - bidManager.addBidResponse('testCode', { mediaType: 'video' }); - bidManager.addBidResponse('testCode', { mediaType: 'native' }); - expect($$PREBID_GLOBAL$$._bidsReceived.length).to.equal(0); - }); - - it('should add bids with a vastUrl and then execute the callbacks signaling the end of the auction', - testAddVideoBid(true, true, stubProvider, false)); - - it('should add bids with a vastXml and then execute the callbacks signaling the end of the auction', - testAddVideoBid(true, true, stubProvider, true)); - - it('should gracefully do nothing when adUnitCode is undefined', () => { - bidManager.addBidResponse(undefined, {}); - expect($$PREBID_GLOBAL$$._bidsReceived.length).to.equal(0); - }); - - it('should gracefully do nothing when bid is undefined', () => { - bidManager.addBidResponse('mock/code'); - expect($$PREBID_GLOBAL$$._bidsReceived.length).to.equal(0); - }); - - it('should attach properties for analytics *before* the BID_ADJUSTMENT event listeners are called', () => { - const copy = Object.assign({}, urlBidResponse); - copy.getSize = function() { - return `${this.height}x${this.width}`; - }; - delete copy.cpm; - bidManager.addBidResponse(adUnit.code, copy); - expect(copy).to.have.property('hadCpmDuringBidAdjustment', true); - expect(copy).to.have.property('hadAdUnitCodeDuringBidAdjustment', true); - expect(copy).to.have.property('hadTimeToRespondDuringBidAdjustment', true); - expect(copy).to.have.property('hadRequestTimestampDuringBidAdjustment', true); - expect(copy).to.have.property('hadResponseTimestampDuringBidAdjustment', true); - }); - }); - - describe('when the auction has timed out', () => { - // Set up the global state to expect two bids, and mock an auction which happened long enough - // in the past that it will *seem* like this bid is arriving after the timeouts. - prepAuction( - [adUnit, Object.assign({}, adUnit, { code: 'video2' })], - (bidRequest) => { - const tweakedBidRequestBid = Object.assign({}, bidRequest.bids[0], { placementCode: 'video2' }); - bidRequest.bids.push(tweakedBidRequestBid); - bidRequest.start = auctionStart(true); - }); - - // Because of the preconditions, this makes sure that the end-of-auction callbacks get called when - // the auction hits the timeout. - it('should add the bid, but also execute the callbacks signaling the end of the auction', - testAddVideoBid(true, true, stubProvider, false)); - }); - }); - - describe('when the cache is failing for some reason,', () => { - let stubProvider = useVideoCacheStubs({ - store: new Error('Unable to save to the cache'), - }); - - describe('when the auction still has time left', () => { - prepAuction([adUnit], (bidRequest) => bidRequest.start = auctionStart(false)); - - it("shouldn't add the bid to the auction, and shouldn't execute the end-of-auction callbacks", - testAddVideoBid(false, false, stubProvider, false)); - }); - - describe('when the auction has timed out', () => { - prepAuction([adUnit], (bidRequest) => bidRequest.start = auctionStart(true)); - it("shouldn't add the bid to the auction, but should execute the end-of-auction callbacks", - testAddVideoBid(false, true, stubProvider, false)); - }) - }); - }); - - describe('when prebid-cache is disabled', () => { - let stubProvider = useVideoCacheStubs({ - store: [{ uuid: 'FAKE_UUID' }], - }); - - before(() => { - config.setConfig({ - usePrebidCache: false, - }); - }); - - describe('when more bids are expected after this one', testAddExpectMoreBids(stubProvider)); - }); - }); -}); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 6da22ed8984..8b1c164a804 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1,10 +1,17 @@ import { expect } from 'chai'; import AdapterManager from 'src/adaptermanager'; +import { checkBidRequestSizes } from 'src/adaptermanager'; import { getAdUnits } from 'test/fixtures/fixtures'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils'; +import { config } from 'src/config'; 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'); +var events = require('../../../../src/events'); +const adloader = require('../../../../src/adloader'); const CONFIG = { enabled: true, @@ -16,37 +23,275 @@ const CONFIG = { }; var prebidServerAdapterMock = { bidder: 'prebidServer', - callBids: sinon.stub(), - setConfig: sinon.stub(), - queueSync: sinon.stub() + callBids: sinon.stub() }; var adequantAdapterMock = { bidder: 'adequant', - callBids: sinon.stub(), - setConfig: sinon.stub(), - queueSync: sinon.stub() + callBids: sinon.stub() }; var appnexusAdapterMock = { bidder: 'appnexus', - callBids: sinon.stub(), - setConfig: sinon.stub(), - queueSync: sinon.stub() + callBids: sinon.stub() }; +var rubiconAdapterMock = { + bidder: 'rubicon', + callBids: sinon.stub() +}; +let loadScriptStub; + describe('adapterManager tests', () => { + let orgAppnexusAdapter; + let orgAdequantAdapter; + let orgPrebidServerAdapter; + let orgRubiconAdapter; + before(() => { + 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](); + }); + }); + + after(() => { + AdapterManager.bidderRegistry['appnexus'] = orgAppnexusAdapter; + AdapterManager.bidderRegistry['adequant'] = orgAdequantAdapter; + AdapterManager.bidderRegistry['prebidServer'] = orgPrebidServerAdapter; + AdapterManager.bidderRegistry['rubicon'] = orgRubiconAdapter; + loadScriptStub.restore(); + config.setConfig({s2sConfig: { enabled: false }}); + }); + + describe('callBids', () => { + before(() => { + config.setConfig({s2sConfig: { enabled: false }}); + }); + + beforeEach(() => { + sinon.stub(utils, 'logError'); + appnexusAdapterMock.callBids.reset(); + AdapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; + }); + + afterEach(() => { + utils.logError.restore(); + delete AdapterManager.bidderRegistry['appnexus']; + }); + + it('should log an error if a bidder is used that does not exist', () => { + const adUnits = [{ + code: 'adUnit-code', + sizes: [[728, 90]], + bids: [ + {bidder: 'appnexus', params: {placementId: 'id'}}, + {bidder: 'fakeBidder', params: {placementId: 'id'}} + ] + }]; + + let bidRequests = AdapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + AdapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + sinon.assert.called(utils.logError); + }); + + it('should emit BID_REQUESTED event', () => { + // function to count BID_REQUESTED events + let cnt = 0; + let count = () => cnt++; + events.on(CONSTANTS.EVENTS.BID_REQUESTED, count); + let bidRequests = [{ + 'bidderCode': 'appnexus', + 'auctionId': '1863e370099523', + 'bidderRequestId': '2946b569352ef2', + 'tid': '34566b569352ef2', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [[728, 90], [970, 70]], + 'bidId': '392b5a6b05d648', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897462, + 'status': 1, + 'transactionId': 'fsafsa' + }, + ], + 'start': 1462918897460 + }]; + + let adUnits = [{ + code: 'adUnit-code', + bids: [ + {bidder: 'appnexus', params: {placementId: 'id'}}, + ] + }]; + AdapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + expect(cnt).to.equal(1); + sinon.assert.calledOnce(appnexusAdapterMock.callBids); + events.off(CONSTANTS.EVENTS.BID_REQUESTED, count); + }); + }); + describe('S2S tests', () => { beforeEach(() => { - AdapterManager.setS2SConfig(CONFIG); + config.setConfig({s2sConfig: CONFIG}); AdapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; - prebidServerAdapterMock.callBids.reset(); }); it('invokes callBids on the S2S adapter', () => { - AdapterManager.callBids({adUnits: getAdUnits()}); + let bidRequests = [{ + 'bidderCode': 'appnexus', + 'auctionId': '1863e370099523', + 'bidderRequestId': '2946b569352ef2', + 'tid': '34566b569352ef2', + 'src': 's2s', + 'adUnitsS2SCopy': [ + { + 'code': '/19968336/header-bid-tag1', + 'sizes': [ + { + 'w': 728, + 'h': 90 + }, + { + 'w': 970, + 'h': 90 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '543221', + 'test': 'me' + }, + 'placementCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '68136e1c47023d', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220995, + 'status': 1, + 'bid_id': '378a8914450b334' + } + ] + }, + { + 'code': '/19968336/header-bid-tag-0', + 'sizes': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '5324321' + }, + 'placementCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '7e5d6af25ed188', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220996, + 'bid_id': '387d9d9c32ca47c' + } + ] + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '392b5a6b05d648', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897462, + 'status': 1, + 'transactionId': 'fsafsa' + }, + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418' + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '4dccdc37746135', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897463, + 'status': 1, + 'transactionId': 'fsafsa' + } + ], + 'start': 1462918897460 + }]; + + AdapterManager.callBids( + getAdUnits(), + bidRequests, + () => {}, + () => () => {} + ); sinon.assert.calledOnce(prebidServerAdapterMock.callBids); }); + // Enable this test when prebidServer adapter is made 1.0 compliant it('invokes callBids with only s2s bids', () => { const adUnits = getAdUnits(); // adUnit without appnexus bidder @@ -63,45 +308,216 @@ describe('adapterManager tests', () => { } ] }); - AdapterManager.callBids({adUnits: adUnits}); + + let bidRequests = [{ + 'bidderCode': 'appnexus', + 'auctionId': '1863e370099523', + 'bidderRequestId': '2946b569352ef2', + 'tid': '34566b569352ef2', + 'src': 's2s', + 'adUnitsS2SCopy': [ + { + 'code': '/19968336/header-bid-tag1', + 'sizes': [ + { + 'w': 728, + 'h': 90 + }, + { + 'w': 970, + 'h': 90 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '543221', + 'test': 'me' + }, + 'placementCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '68136e1c47023d', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220995, + 'status': 1, + 'bid_id': '378a8914450b334' + } + ] + }, + { + 'code': '/19968336/header-bid-tag-0', + 'sizes': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '5324321' + }, + 'placementCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '7e5d6af25ed188', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220996, + 'bid_id': '387d9d9c32ca47c' + } + ] + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '392b5a6b05d648', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897462, + 'status': 1, + 'transactionId': 'fsafsa' + }, + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418' + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '4dccdc37746135', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897463, + 'status': 1, + 'transactionId': 'fsafsa' + } + ], + 'start': 1462918897460 + }]; + AdapterManager.callBids( + adUnits, + bidRequests, + () => {}, + () => () => {} + ); const requestObj = prebidServerAdapterMock.callBids.firstCall.args[0]; expect(requestObj.ad_units.length).to.equal(2); sinon.assert.calledOnce(prebidServerAdapterMock.callBids); }); - }); // end s2s tests - describe('The setBidderSequence() function', () => { - let spy; + describe('BID_REQUESTED event', () => { + // function to count BID_REQUESTED events + let cnt, count = () => cnt++; - beforeEach(() => { - spy = sinon.spy(utils, 'logWarn') - }); + beforeEach(() => { + prebidServerAdapterMock.callBids.reset(); + cnt = 0; + events.on(CONSTANTS.EVENTS.BID_REQUESTED, count); + }); - afterEach(() => { - utils.logWarn.restore(); - }); + afterEach(() => { + events.off(CONSTANTS.EVENTS.BID_REQUESTED, count); + }); - it('should log a warning on invalid values', () => { - AdapterManager.setBidderSequence('unrecognized sequence'); - expect(spy.calledOnce).to.equal(true); - }); + it('should fire for s2s requests', () => { + let adUnits = utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus'], bid.bidder)); + return adUnit; + }) + let bidRequests = AdapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + AdapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + expect(cnt).to.equal(1); + sinon.assert.calledOnce(prebidServerAdapterMock.callBids); + }); - it('should not log warnings when given recognized values', () => { - AdapterManager.setBidderSequence('fixed'); - AdapterManager.setBidderSequence('random'); - expect(spy.called).to.equal(false); + it('should fire for simultaneous s2s and client requests', () => { + 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, () => {}, () => {}); + expect(cnt).to.equal(2); + sinon.assert.calledOnce(prebidServerAdapterMock.callBids); + sinon.assert.calledOnce(adequantAdapterMock.callBids); + adequantAdapterMock.callBids.reset(); + delete AdapterManager.bidderRegistry['adequant']; + }); }); - }) + }); // end s2s tests describe('s2sTesting', () => { + let doneStub = sinon.stub(); + let ajaxStub = sinon.stub(); + function getTestAdUnits() { // copy adUnits - return JSON.parse(JSON.stringify(getAdUnits())); + // return JSON.parse(JSON.stringify(getAdUnits())); + return utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => includes(['adequant', 'appnexus', 'rubicon'], bid.bidder)); + return adUnit; + }) + } + + function callBids(adUnits = getTestAdUnits()) { + let bidRequests = AdapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + AdapterManager.callBids(adUnits, bidRequests, doneStub, ajaxStub); } function checkServerCalled(numAdUnits, numBids) { sinon.assert.calledOnce(prebidServerAdapterMock.callBids); - var requestObj = prebidServerAdapterMock.callBids.firstCall.args[0]; + let requestObj = prebidServerAdapterMock.callBids.firstCall.args[0]; expect(requestObj.ad_units.length).to.equal(numAdUnits); for (let i = 0; i < numAdUnits; i++) { expect(requestObj.ad_units[i].bids.filter((bid) => { @@ -115,35 +531,36 @@ describe('adapterManager tests', () => { expect(adapter.callBids.firstCall.args[0].bids.length).to.equal(numBids); } - var TESTING_CONFIG; - var stubGetSourceBidderMap; + let TESTING_CONFIG = utils.deepClone(CONFIG); + Object.assign(TESTING_CONFIG, { + bidders: ['appnexus', 'adequant'], + testing: true + }); + let stubGetSourceBidderMap; beforeEach(() => { - TESTING_CONFIG = Object.assign(CONFIG, { - bidders: ['appnexus', 'adequant'], - testing: true - }); - - AdapterManager.setS2SConfig(CONFIG); + config.setConfig({s2sConfig: TESTING_CONFIG}); AdapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; AdapterManager.bidderRegistry['adequant'] = adequantAdapterMock; AdapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; + AdapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; stubGetSourceBidderMap = sinon.stub(s2sTesting, 'getSourceBidderMap'); prebidServerAdapterMock.callBids.reset(); adequantAdapterMock.callBids.reset(); appnexusAdapterMock.callBids.reset(); + rubiconAdapterMock.callBids.reset(); }); afterEach(() => { + config.setConfig({s2sConfig: {}}); s2sTesting.getSourceBidderMap.restore(); }); it('calls server adapter if no sources defined', () => { stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: [], [s2sTesting.SERVER]: []}); - AdapterManager.setS2SConfig(TESTING_CONFIG); - AdapterManager.callBids({adUnits: getTestAdUnits()}); + callBids(); // server adapter checkServerCalled(2, 2); @@ -157,8 +574,7 @@ describe('adapterManager tests', () => { it('calls client adapter if one client source defined', () => { stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus'], [s2sTesting.SERVER]: []}); - AdapterManager.setS2SConfig(TESTING_CONFIG); - AdapterManager.callBids({adUnits: getTestAdUnits()}); + callBids(); // server adapter checkServerCalled(2, 2); @@ -172,8 +588,21 @@ describe('adapterManager tests', () => { it('calls client adapters if client sources defined', () => { stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); - AdapterManager.setS2SConfig(TESTING_CONFIG); - AdapterManager.callBids({adUnits: getTestAdUnits()}); + callBids(); + + // server adapter + checkServerCalled(2, 2); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + checkClientCalled(adequantAdapterMock, 2); + }); + + it('calls client adapters if client sources defined', () => { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + callBids(); // server adapter checkServerCalled(2, 2); @@ -187,13 +616,12 @@ describe('adapterManager tests', () => { it('does not call server adapter for bidders that go to client', () => { stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); - AdapterManager.setS2SConfig(TESTING_CONFIG); var adUnits = getTestAdUnits(); adUnits[0].bids[0].finalSource = s2sTesting.CLIENT; adUnits[0].bids[1].finalSource = s2sTesting.CLIENT; adUnits[1].bids[0].finalSource = s2sTesting.CLIENT; adUnits[1].bids[1].finalSource = s2sTesting.CLIENT; - AdapterManager.callBids({adUnits}); + callBids(adUnits); // server adapter sinon.assert.notCalled(prebidServerAdapterMock.callBids); @@ -207,13 +635,12 @@ describe('adapterManager tests', () => { it('does not call client adapters for bidders that go to server', () => { stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); - AdapterManager.setS2SConfig(TESTING_CONFIG); var adUnits = getTestAdUnits(); adUnits[0].bids[0].finalSource = s2sTesting.SERVER; adUnits[0].bids[1].finalSource = s2sTesting.SERVER; adUnits[1].bids[0].finalSource = s2sTesting.SERVER; adUnits[1].bids[1].finalSource = s2sTesting.SERVER; - AdapterManager.callBids({adUnits}); + callBids(adUnits); // server adapter checkServerCalled(2, 2); @@ -227,13 +654,12 @@ describe('adapterManager tests', () => { it('calls client and server adapters for bidders that go to both', () => { stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); - AdapterManager.setS2SConfig(TESTING_CONFIG); 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; - AdapterManager.callBids({adUnits}); + callBids(adUnits); // server adapter checkServerCalled(2, 2); @@ -247,13 +673,12 @@ describe('adapterManager tests', () => { it('makes mixed client/server adapter calls for mixed bidder sources', () => { stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); - AdapterManager.setS2SConfig(TESTING_CONFIG); var adUnits = getTestAdUnits(); adUnits[0].bids[0].finalSource = s2sTesting.CLIENT; adUnits[0].bids[1].finalSource = s2sTesting.CLIENT; adUnits[1].bids[0].finalSource = s2sTesting.SERVER; adUnits[1].bids[1].finalSource = s2sTesting.SERVER; - AdapterManager.callBids({adUnits}); + callBids(adUnits); // server adapter checkServerCalled(1, 2); @@ -269,33 +694,6 @@ describe('adapterManager tests', () => { describe('aliasBidderAdaptor', function() { const CODE = 'sampleBidder'; - // Note: remove this describe once Prebid is 1.0 - describe('old way', function() { - let originalRegistry; - - function SampleAdapter() { - return Object.assign(this, { - callBids: sinon.stub(), - setBidderCode: sinon.stub() - }); - } - - before(() => { - originalRegistry = AdapterManager.bidderRegistry; - AdapterManager.bidderRegistry[CODE] = new SampleAdapter(); - }); - - after(() => { - AdapterManager.bidderRegistry = originalRegistry; - }); - - it('should add alias to registry', () => { - const alias = 'testalias'; - AdapterManager.aliasBidAdapter(CODE, alias); - expect(AdapterManager.bidderRegistry).to.have.property(alias); - }); - }); - describe('using bidderFactory', function() { let spec; @@ -319,4 +717,363 @@ describe('adapterManager tests', () => { }); }); }); + + describe('makeBidRequests', () => { + let adUnits; + beforeEach(() => { + adUnits = utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'rubicon'], bid.bidder)); + return adUnit; + }) + }); + + describe('setBidderSequence', () => { + beforeEach(() => { + sinon.spy(utils, 'shuffle'); + }); + + afterEach(() => { + config.resetConfig(); + utils.shuffle.restore(); + }); + + it('setting to `random` uses shuffled order of adUnits', () => { + config.setConfig({ bidderSequence: 'random' }); + let bidRequests = AdapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + sinon.assert.calledOnce(utils.shuffle); + }); + }); + + describe('sizeMapping', () => { + beforeEach(() => { + sinon.stub(window, 'matchMedia').callsFake(() => ({matches: true})); + }); + + afterEach(() => { + matchMedia.restore(); + setSizeConfig([]); + }); + + it('should not filter bids w/ no labels', () => { + let bidRequests = AdapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + + 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); + + 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); + }); + + it('should filter sizes using size config', () => { + let validSizes = [ + [728, 90], + [300, 250] + ]; + + let validSizeMap = validSizes.map(size => size.toString()).reduce((map, size) => { + map[size] = true; + return map; + }, {}); + + setSizeConfig([{ + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': validSizes, + 'labels': ['tablet', 'phone'] + }]); + + let bidRequests = AdapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + + // only valid sizes as specified in size config should show up in bidRequests + bidRequests.forEach(bidRequest => { + bidRequest.bids.forEach(bid => { + bid.sizes.forEach(size => { + expect(validSizeMap[size]).to.equal(true); + }); + }); + }); + + setSizeConfig([{ + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': [], + 'labels': ['tablet', 'phone'] + }]); + + bidRequests = AdapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + + // if no valid sizes, all bidders should be filtered out + expect(bidRequests.length).to.equal(0); + }); + + it('should filter adUnits/bidders based on applied labels', () => { + adUnits[0].labelAll = ['visitor-uk', 'mobile']; + adUnits[1].labelAny = ['visitor-uk', 'desktop']; + adUnits[1].bids[0].labelAny = ['mobile']; + adUnits[1].bids[1].labelAll = ['desktop']; + + let bidRequests = AdapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + ['visitor-uk', 'desktop'] + ); + + // only one adUnit and one bid from that adUnit should make it through the applied labels above + expect(bidRequests.length).to.equal(1); + expect(bidRequests[0].bidderCode).to.equal('rubicon'); + expect(bidRequests[0].bids.length).to.equal(1); + expect(bidRequests[0].bids[0].adUnitCode).to.equal(adUnits[1].code); + }); + }); + + describe('gdpr consent module', () => { + it('inserts gdprConsent object to bidRequest only when module was enabled', () => { + AdapterManager.gdprDataHandler.setConsentData({ + consentString: 'abc123def456', + consentRequired: true + }); + + let bidRequests = AdapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + expect(bidRequests[0].gdprConsent.consentString).to.equal('abc123def456'); + expect(bidRequests[0].gdprConsent.consentRequired).to.be.true; + + AdapterManager.gdprDataHandler.setConsentData(null); + + bidRequests = AdapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + expect(bidRequests[0].gdprConsent).to.be.undefined; + }); + }); + }); + + describe('isValidBidRequest', () => { + describe('positive tests for validating bid request', () => { + beforeEach(() => { + sinon.stub(utils, 'logInfo'); + }); + + afterEach(() => { + utils.logInfo.restore(); + }); + + it('should maintain adUnit structure and adUnits.sizes is replaced', () => { + 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; + + 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; + + 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); + }); + }); + + describe('negative tests for validating bid requests', () => { + beforeEach(() => { + sinon.stub(utils, 'logError'); + }); + + afterEach(() => { + utils.logError.restore(); + }); + + it('should throw error message and delete an object/property', () => { + 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); + + 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); + + 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); + + 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); + + 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); + }); + }); + }); }); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index e91ddcf39a4..ca2a9afc103 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1,24 +1,27 @@ import { newBidder, registerBidder } from 'src/adapters/bidderFactory'; -import bidmanager from 'src/bidmanager'; import adaptermanager from 'src/adaptermanager'; import * as ajax from 'src/ajax'; import { expect } from 'chai'; import { STATUS } from 'src/constants'; import { userSync } from 'src/userSync' +import * as utils from 'src/utils'; +import { config } from 'src/config'; const CODE = 'sampleBidder'; const MOCK_BIDS_REQUEST = { bids: [ { - requestId: 'first-bid-id', - placementCode: 'mock/placement', + bidId: 1, + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', params: { param: 5 } }, { - requestId: 'second-bid-id', - placementCode: 'mock/placement2', + bidId: 2, + auctionId: 'second-bid-id', + adUnitCode: 'mock/placement2', params: { badParam: 6 } @@ -28,8 +31,9 @@ const MOCK_BIDS_REQUEST = { describe('bidders created by newBidder', () => { let spec; - let addBidRequestStub; let bidder; + let addBidResponseStub; + let doneStub; beforeEach(() => { spec = { @@ -39,11 +43,9 @@ describe('bidders created by newBidder', () => { interpretResponse: sinon.stub(), getUserSyncs: sinon.stub() }; - addBidRequestStub = sinon.stub(bidmanager, 'addBidResponse'); - }); - afterEach(() => { - addBidRequestStub.restore(); + addBidResponseStub = sinon.stub(); + doneStub = sinon.stub(); }); describe('when the ajax response is irrelevant', () => { @@ -51,6 +53,8 @@ describe('bidders created by newBidder', () => { beforeEach(() => { ajaxStub = sinon.stub(ajax, 'ajax'); + addBidResponseStub.reset(); + doneStub.reset(); }); afterEach(() => { @@ -63,7 +67,7 @@ describe('bidders created by newBidder', () => { spec.getUserSyncs.returns([]); bidder.callBids({}); - bidder.callBids({ bids: 'nothing useful' }); + bidder.callBids({ bids: 'nothing useful' }, addBidResponseStub, doneStub, ajaxStub); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.called).to.equal(false); @@ -77,7 +81,7 @@ describe('bidders created by newBidder', () => { spec.isBidRequestValid.returns(true); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.calledTwice).to.equal(true); @@ -91,7 +95,7 @@ describe('bidders created by newBidder', () => { spec.isBidRequestValid.returns(false); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.calledTwice).to.equal(true); @@ -105,7 +109,7 @@ describe('bidders created by newBidder', () => { spec.isBidRequestValid.onSecondCall().returns(false); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.calledTwice).to.equal(true); @@ -113,13 +117,13 @@ describe('bidders created by newBidder', () => { expect(spec.buildRequests.firstCall.args[0]).to.deep.equal([MOCK_BIDS_REQUEST.bids[0]]); }); - it("should make no server requests if the spec doesn't return any", () => { + it('should make no server requests if the spec doesn\'t return any', () => { const bidder = newBidder(spec); spec.isBidRequestValid.returns(true); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(ajaxStub.called).to.equal(false); }); @@ -135,7 +139,7 @@ describe('bidders created by newBidder', () => { data: data }); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(url); @@ -151,7 +155,7 @@ describe('bidders created by newBidder', () => { const bidder = newBidder(spec); const url = 'test.url.com'; const data = { arg: 2 }; - const options = { contentType: 'application/json'}; + const options = { contentType: 'application/json' }; spec.isBidRequestValid.returns(true); spec.buildRequests.returns({ method: 'POST', @@ -160,7 +164,7 @@ describe('bidders created by newBidder', () => { options: options }); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(url); @@ -183,7 +187,7 @@ describe('bidders created by newBidder', () => { data: data }); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2&`); @@ -207,7 +211,7 @@ describe('bidders created by newBidder', () => { options: opt }); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2&`); @@ -236,12 +240,12 @@ describe('bidders created by newBidder', () => { } ]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(ajaxStub.calledTwice).to.equal(true); }); - it('should add bids for each placement code if no requests are given', () => { + it('should not add bids for each placement code if no requests are given', () => { const bidder = newBidder(spec); spec.isBidRequestValid.returns(true); @@ -249,33 +253,36 @@ describe('bidders created by newBidder', () => { spec.interpretResponse.returns([]); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); - expect(bidmanager.addBidResponse.calledTwice).to.equal(true); - const placementsWithBids = - [bidmanager.addBidResponse.firstCall.args[0], bidmanager.addBidResponse.secondCall.args[0]]; - expect(placementsWithBids).to.contain('mock/placement'); - expect(placementsWithBids).to.contain('mock/placement2'); + expect(addBidResponseStub.callCount).to.equal(0); }); }); describe('when the ajax call succeeds', () => { let ajaxStub; let userSyncStub; + let logErrorSpy; beforeEach(() => { - ajaxStub = sinon.stub(ajax, 'ajax', function(url, callbacks) { - callbacks.success('response body'); + ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { + const fakeResponse = sinon.stub(); + fakeResponse.returns('headerContent'); + callbacks.success('response body', { getResponseHeader: fakeResponse }); }); + addBidResponseStub.reset(); + doneStub.resetBehavior(); userSyncStub = sinon.stub(userSync, 'registerSync') + logErrorSpy = sinon.spy(utils, 'logError'); }); afterEach(() => { ajaxStub.restore(); userSyncStub.restore(); + utils.logError.restore(); }); - it('should call spec.interpretResponse() with the response body content', () => { + it('should call spec.interpretResponse() with the response content', () => { const bidder = newBidder(spec); spec.isBidRequestValid.returns(true); @@ -286,15 +293,18 @@ describe('bidders created by newBidder', () => { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(spec.interpretResponse.calledOnce).to.equal(true); - expect(spec.interpretResponse.firstCall.args[0]).to.equal('response body'); + const response = spec.interpretResponse.firstCall.args[0] + expect(response.body).to.equal('response body') + expect(response.headers.get('some-header')).to.equal('headerContent'); expect(spec.interpretResponse.firstCall.args[1]).to.deep.equal({ method: 'POST', url: 'test.url.com', data: {} }); + expect(doneStub.calledOnce).to.equal(true); }); it('should call spec.interpretResponse() once for each request made', () => { @@ -315,21 +325,26 @@ describe('bidders created by newBidder', () => { ]); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(spec.interpretResponse.calledTwice).to.equal(true); + expect(doneStub.calledOnce).to.equal(true); }); - it("should add bids for each placement code into the bidmanager, even if the bidder doesn't bid on all of them", () => { + it('should only add bids for valid adUnit code into the auction, even if the bidder doesn\'t bid on all of them', () => { const bidder = newBidder(spec); const bid = { - requestId: 'some-id', + creativeId: 'creative-id', + requestId: '1', ad: 'ad-url.com', cpm: 0.5, height: 200, width: 300, - placementCode: 'mock/placement' + adUnitCode: 'mock/placement', + currency: 'USD', + netRevenue: true, + ttl: 300 }; spec.isBidRequestValid.returns(true); spec.buildRequests.returns({ @@ -341,13 +356,12 @@ describe('bidders created by newBidder', () => { spec.interpretResponse.returns(bid); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); - expect(bidmanager.addBidResponse.calledTwice).to.equal(true); - const placementsWithBids = - [bidmanager.addBidResponse.firstCall.args[0], bidmanager.addBidResponse.secondCall.args[0]]; - expect(placementsWithBids).to.contain('mock/placement'); - expect(placementsWithBids).to.contain('mock/placement2'); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + expect(doneStub.calledOnce).to.equal(true); + expect(logErrorSpy.callCount).to.equal(0); }); it('should call spec.getUserSyncs() with the response', () => { @@ -361,10 +375,13 @@ describe('bidders created by newBidder', () => { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(spec.getUserSyncs.calledOnce).to.equal(true); - expect(spec.getUserSyncs.firstCall.args[1]).to.deep.equal(['response body']); + expect(spec.getUserSyncs.firstCall.args[1].length).to.equal(1); + expect(spec.getUserSyncs.firstCall.args[1][0].body).to.equal('response body'); + expect(spec.getUserSyncs.firstCall.args[1][0].headers).to.have.property('get'); + expect(spec.getUserSyncs.firstCall.args[1][0].headers.get).to.be.a('function'); }); it('should register usersync pixels', () => { @@ -377,22 +394,50 @@ describe('bidders created by newBidder', () => { url: 'usersync.com' }]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(userSyncStub.called).to.equal(true); expect(userSyncStub.firstCall.args[0]).to.equal('iframe'); expect(userSyncStub.firstCall.args[1]).to.equal(spec.code); expect(userSyncStub.firstCall.args[2]).to.equal('usersync.com'); }); + + it('should logError when required bid response params are missing', () => { + const bidder = newBidder(spec); + + const bid = { + requestId: '1', + ad: 'ad-url.com', + cpm: 0.5, + height: 200, + width: 300, + placementCode: 'mock/placement' + }; + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + spec.getUserSyncs.returns([]); + + spec.interpretResponse.returns(bid); + + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + + expect(logErrorSpy.calledOnce).to.equal(true); + }); }); describe('when the ajax call fails', () => { let ajaxStub; beforeEach(() => { - ajaxStub = sinon.stub(ajax, 'ajax', function(url, callbacks) { + ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { callbacks.error('ajax call failed.'); }); + addBidResponseStub.reset(); + doneStub.reset(); }); afterEach(() => { @@ -410,12 +455,13 @@ describe('bidders created by newBidder', () => { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(spec.interpretResponse.called).to.equal(false); + expect(doneStub.calledOnce).to.equal(true); }); - it('should add bids for each placement code into the bidmanager', () => { + it('should not add bids for each adunit code into the auction', () => { const bidder = newBidder(spec); spec.isBidRequestValid.returns(true); @@ -427,13 +473,10 @@ describe('bidders created by newBidder', () => { spec.interpretResponse.returns([]); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); - expect(bidmanager.addBidResponse.calledTwice).to.equal(true); - const placementsWithBids = - [bidmanager.addBidResponse.firstCall.args[0], bidmanager.addBidResponse.secondCall.args[0]]; - expect(placementsWithBids).to.contain('mock/placement'); - expect(placementsWithBids).to.contain('mock/placement2'); + expect(addBidResponseStub.callCount).to.equal(0); + expect(doneStub.calledOnce).to.equal(true); }); it('should call spec.getUserSyncs() with no responses', () => { @@ -447,10 +490,11 @@ describe('bidders created by newBidder', () => { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); expect(spec.getUserSyncs.calledOnce).to.equal(true); expect(spec.getUserSyncs.firstCall.args[1]).to.deep.equal([]); + expect(doneStub.calledOnce).to.equal(true); }); }); }); @@ -511,3 +555,193 @@ describe('registerBidder', () => { expect(registerBidAdapterStub.thirdCall.args[1]).to.equal('bar') }); }) + +describe('validate bid response: ', () => { + let spec; + let bidder; + let addBidResponseStub; + let doneStub; + let ajaxStub; + let logErrorSpy; + + let bids = [{ + 'ad': 'creative', + 'cpm': '1.99', + 'width': 300, + 'height': 250, + 'requestId': '1', + 'creativeId': 'some-id', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360 + }]; + + beforeEach(() => { + spec = { + code: CODE, + isBidRequestValid: sinon.stub(), + buildRequests: sinon.stub(), + interpretResponse: sinon.stub(), + }; + + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + + addBidResponseStub = sinon.stub(); + doneStub = sinon.stub(); + ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { + const fakeResponse = sinon.stub(); + fakeResponse.returns('headerContent'); + callbacks.success('response body', { getResponseHeader: fakeResponse }); + }); + logErrorSpy = sinon.spy(utils, 'logError'); + }); + + afterEach(() => { + ajaxStub.restore(); + logErrorSpy.restore(); + }); + + it('should add native bids that do have required assets', () => { + let bidRequest = { + bids: [{ + bidId: 1, + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + params: { + param: 5 + }, + nativeParams: { + title: {'required': true}, + }, + mediaType: 'native', + }] + }; + + let bids1 = Object.assign({}, + bids[0], + { + 'mediaType': 'native', + 'native': { + 'title': 'Native Creative', + 'clickUrl': 'https://www.link.example', + } + } + ); + + const bidder = newBidder(spec); + + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub); + + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + expect(logErrorSpy.callCount).to.equal(0); + }); + + it('should not add native bids that do not have required assets', () => { + let bidRequest = { + bids: [{ + bidId: 1, + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + params: { + param: 5 + }, + nativeParams: { + title: {'required': true}, + }, + mediaType: 'native', + }] + }; + + let bids1 = Object.assign({}, + bids[0], + { + bidderCode: CODE, + mediaType: 'native', + native: { + title: undefined, + clickUrl: 'https://www.link.example', + } + } + ); + + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub); + + expect(addBidResponseStub.calledOnce).to.equal(false); + expect(logErrorSpy.callCount).to.equal(1); + }); + + it('should add bid when renderer is present on outstream bids', () => { + let bidRequest = { + bids: [{ + bidId: 1, + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + params: { + param: 5 + }, + mediaTypes: { + video: {context: 'outstream'} + } + }] + }; + + let bids1 = Object.assign({}, + bids[0], + { + bidderCode: CODE, + mediaType: 'video', + renderer: {render: () => true, url: 'render.js'}, + } + ); + + const bidder = newBidder(spec); + + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub); + + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + expect(logErrorSpy.callCount).to.equal(0); + }); + + it('should add banner bids that have no width or height but single adunit size', () => { + let bidRequest = { + bids: [{ + bidder: CODE, + bidId: 1, + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + params: { + param: 5 + }, + sizes: [[300, 250]], + }] + }; + + let bids1 = Object.assign({}, + bids[0], + { + width: undefined, + height: undefined + } + ); + + const bidder = newBidder(spec); + + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub); + + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + expect(logErrorSpy.callCount).to.equal(0); + }); +}); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index f705a46a7f7..1fa648e9825 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -1,8 +1,11 @@ import { expect } from 'chai'; -import Targeting from 'src/targeting'; +import { targeting as targetingInstance } from 'src/targeting'; import { config } from 'src/config'; -import { getAdUnits } from 'test/fixtures/fixtures'; +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 = { 'bidderCode': 'rubicon', @@ -28,7 +31,10 @@ const bid1 = { 'hb_adid': '148018fe5e', 'hb_pb': '0.53', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }; const bid2 = { @@ -55,45 +61,175 @@ const bid2 = { 'hb_adid': '5454545', 'hb_pb': '0.25', 'foobar': '300x250' - } + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 +}; + +const bid3 = { + 'bidderCode': 'rubicon', + 'width': '300', + 'height': '600', + 'statusMessage': 'Bid available', + 'adId': '48747745', + 'cpm': 0.75, + 'ad': 'markup', + 'ad_id': '3163950', + 'sizeId': '15', + 'requestTimestamp': 1454535718610, + 'responseTimestamp': 1454535724863, + 'timeToRespond': 123, + 'pbLg': '0.75', + 'pbMg': '0.75', + 'pbHg': '0.75', + 'adUnitCode': '/123456/header-bid-tag-1', + 'bidder': 'rubicon', + 'size': '300x600', + 'adserverTargeting': { + 'hb_bidder': 'rubicon', + 'hb_adid': '48747745', + 'hb_pb': '0.75', + 'foobar': '300x600' + }, + 'netRevenue': true, + 'currency': 'USD', + 'ttl': 300 }; describe('targeting tests', () => { describe('getAllTargeting', () => { + let amBidsReceivedStub; + let amGetAdUnitsStub; + let bidExpiryStub; + beforeEach(() => { $$PREBID_GLOBAL$$._sendAllBids = false; - $$PREBID_GLOBAL$$._bidsReceived = []; - $$PREBID_GLOBAL$$._adUnitCodes = []; - $$PREBID_GLOBAL$$.adUnits = []; + amBidsReceivedStub = sinon.stub(auctionManager, 'getBidsReceived').callsFake(function() { + return [bid1, bid2, bid3]; + }); + amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { + return ['/123456/header-bid-tag-0']; + }); + bidExpiryStub = sinon.stub(targetingModule, 'isBidExpired').returns(true); + }); + + afterEach(() => { + auctionManager.getBidsReceived.restore(); + auctionManager.getAdUnitCodes.restore(); + targetingModule.isBidExpired.restore(); }); it('selects the top bid when _sendAllBids true', () => { - $$PREBID_GLOBAL$$.adUnits = [{ - code: '/123456/header-bid-tag-0', - sizes: [300, 250], - bids: [ - { - 'bidder': 'rubicon', - 'params': { - 'accountId': 10617, - 'siteId': 23635, - 'zoneId': 453908 - } - } - ] - }]; config.setConfig({ enableSendAllBids: true }); - $$PREBID_GLOBAL$$._bidsReceived.push(bid1, bid2); - $$PREBID_GLOBAL$$._adUnitCodes = ['/123456/header-bid-tag-0']; - let targeting = Targeting.getAllTargeting(['/123456/header-bid-tag-0']); - let flattened = []; - targeting.filter(obj => obj['/123456/header-bid-tag-0'] !== undefined).forEach(item => flattened = flattened.concat(item['/123456/header-bid-tag-0'])); - let sendAllBidCpm = flattened.filter(obj => obj.hb_pb_rubicon !== undefined); - let winningBidCpm = flattened.filter(obj => obj.hb_pb !== undefined); + 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) // 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(sendAllBidCpm[0]['hb_pb_rubicon']).to.deep.equal(winningBidCpm[0]['hb_pb']); + expect(targeting['/123456/header-bid-tag-0']['hb_pb_rubicon']).to.deep.equal(targeting['/123456/header-bid-tag-0']['hb_pb']); }); }); // end getAllTargeting tests + + describe('Targeting in concurrent auctions', () => { + describe('check getOldestBid', () => { + let bidExpiryStub; + let auctionManagerStub; + beforeEach(() => { + bidExpiryStub = sinon.stub(targetingModule, 'isBidExpired').returns(true); + auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived'); + }); + + afterEach(() => { + bidExpiryStub.restore(); + auctionManagerStub.restore(); + }); + + it('should use bids from pool to get Winning Bid', () => { + let bidsReceived = [ + createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1'}), + 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'}), + ]; + let adUnitCodes = ['code-0', 'code-1']; + + let bids = targetingInstance.getWinningBids(adUnitCodes, bidsReceived); + + expect(bids.length).to.equal(2); + expect(bids[0].adId).to.equal('adid-1'); + expect(bids[1].adId).to.equal('adid-2'); + }); + + it('should not use rendered bid to get winning bid', () => { + let bidsReceived = [ + createBidReceived({bidder: 'appnexus', cpm: 8, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1', status: 'rendered'}), + createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-1', adId: 'adid-2'}), + createBidReceived({bidder: 'appnexus', cpm: 7, 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'}), + ]; + auctionManagerStub.returns(bidsReceived); + + let adUnitCodes = ['code-0', 'code-1']; + let bids = targetingInstance.getWinningBids(adUnitCodes); + + expect(bids.length).to.equal(2); + expect(bids[0].adId).to.equal('adid-2'); + expect(bids[1].adId).to.equal('adid-3'); + }); + + it('should use oldest bids from bid pool to get winning bid', () => { + // Pool is having 4 bids from 2 auctions. There are 2 bids from rubicon, #2 which is first bid will be selected to take part in auction. + let bidsReceived = [ + createBidReceived({bidder: 'appnexus', cpm: 8, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1'}), + createBidReceived({bidder: 'rubicon', cpm: 9, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-0', adId: 'adid-2'}), + createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-3'}), + createBidReceived({bidder: 'rubicon', cpm: 10, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-0', adId: 'adid-4'}), + ]; + auctionManagerStub.returns(bidsReceived); + + let adUnitCodes = ['code-0']; + let bids = targetingInstance.getWinningBids(adUnitCodes); + + expect(bids.length).to.equal(1); + expect(bids[0].adId).to.equal('adid-2'); + }); + }); + + describe('check bidExpiry', () => { + let auctionManagerStub; + let timestampStub; + beforeEach(() => { + auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived'); + timestampStub = sinon.stub(utils, 'timestamp'); + }); + + afterEach(() => { + auctionManagerStub.restore(); + timestampStub.restore(); + }); + it('should not include expired bids in the auction', () => { + 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. + let bidsReceived = [ + createBidReceived({bidder: 'appnexus', cpm: 18, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1', ttl: 150}), + createBidReceived({bidder: 'sampleBidder', cpm: 16, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-0', adId: 'adid-2', ttl: 100}), + createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-3', ttl: 300}), + createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-0', adId: 'adid-4', ttl: 50}), + ]; + auctionManagerStub.returns(bidsReceived); + + let adUnitCodes = ['code-0', 'code-1']; + let bids = targetingInstance.getWinningBids(adUnitCodes); + + expect(bids.length).to.equal(1); + expect(bids[0].adId).to.equal('adid-3'); + }); + }); + }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index d6f1451b65c..ff7b261117c 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -5,9 +5,16 @@ import { getBidResponsesFromAPI, getTargetingKeys, getTargetingKeysBidLandscape, - getAdUnits + getAdUnits, + createBidReceived } from 'test/fixtures/fixtures'; +import { auctionManager, newAuctionManager } from 'src/auctionManager'; +import { targeting, newTargeting } 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'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -16,7 +23,7 @@ var urlParse = require('url-parse'); var prebid = require('src/prebid'); var utils = require('src/utils'); -var bidmanager = require('src/bidmanager'); +// var bidmanager = require('src/bidmanager'); var bidfactory = require('src/bidfactory'); var adloader = require('src/adloader'); var adaptermanager = require('src/adaptermanager'); @@ -25,24 +32,27 @@ var adserver = require('src/adserver'); var CONSTANTS = require('src/constants.json'); // These bid adapters are required to be loaded for the following tests to work -require('modules/appnexusAstBidAdapter'); -require('modules/adequantBidAdapter'); +require('modules/appnexusBidAdapter'); var config = require('test/fixtures/config.json'); $$PREBID_GLOBAL$$ = $$PREBID_GLOBAL$$ || {}; -$$PREBID_GLOBAL$$._bidsRequested = getBidRequests(); -$$PREBID_GLOBAL$$._bidsReceived = getBidResponses(); -$$PREBID_GLOBAL$$.adUnits = getAdUnits(); -$$PREBID_GLOBAL$$._adUnitCodes = $$PREBID_GLOBAL$$.adUnits.map(unit => unit.code); +var adUnits = getAdUnits(); +var adUnitCodes = getAdUnits().map(unit => unit.code); +var bidsBackHandler = function() {}; +const timeout = 2000; +var auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout: timeout}); +auction.getBidRequests = getBidRequests; +auction.getBidsReceived = getBidResponses; +auction.getAdUnits = getAdUnits; +auction.getAuctionStatus = function() { return auctionModule.AUCTION_COMPLETED } function resetAuction() { $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: false }); - $$PREBID_GLOBAL$$.clearAuction(); - $$PREBID_GLOBAL$$._bidsRequested = getBidRequests(); - $$PREBID_GLOBAL$$._bidsReceived = getBidResponses(); - $$PREBID_GLOBAL$$.adUnits = getAdUnits(); - $$PREBID_GLOBAL$$._adUnitCodes = $$PREBID_GLOBAL$$.adUnits.map(unit => unit.code); + auction.getBidRequests = getBidRequests; + auction.getBidsReceived = getBidResponses; + auction.getAdUnits = getAdUnits; + auction.getAuctionStatus = function() { return auctionModule.AUCTION_COMPLETED } } var Slot = function Slot(elementId, pathId) { @@ -138,9 +148,16 @@ window.apntag = { }; describe('Unit: Prebid Module', function () { + let bidExpiryStub; + before(() => { + bidExpiryStub = sinon.stub(targetingModule, 'isBidExpired').callsFake(() => true); + }); + after(function() { $$PREBID_GLOBAL$$.adUnits = []; + targetingModule.isBidExpired.restore(); }); + describe('getAdserverTargetingForAdUnitCodeStr', function () { beforeEach(() => { resetAuction(); @@ -149,7 +166,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=300x250&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&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 result = $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr(adUnitCode); assert.equal(expected, result, 'returns expected string of ad targeting info'); }); @@ -194,7 +211,7 @@ describe('Unit: Prebid Module', function () { var targeting = $$PREBID_GLOBAL$$.getAdserverTargeting(); var expected = { '/19968336/header-bid-tag-0': { - foobar: '300x250', + foobar: '0x0,300x250,300x600', hb_size: '300x250', hb_pb: '10.00', hb_adid: '233bcbee889d46d', @@ -218,17 +235,19 @@ describe('Unit: Prebid Module', function () { assert.deepEqual(targeting, expected); }); - it("should include a losing bid's custom ad targeting key when the bid has `alwaysUseBid` set to `true`", () => { + it("should include a losing bid's custom ad targeting key", () => { // Let's make sure we're getting the expected losing bid. - assert.equal($$PREBID_GLOBAL$$._bidsReceived[0]['bidderCode'], 'triplelift'); - assert.equal($$PREBID_GLOBAL$$._bidsReceived[0]['cpm'], 0.112256); + assert.equal(auction.getBidsReceived()[0]['bidderCode'], 'triplelift'); + assert.equal(auction.getBidsReceived()[0]['cpm'], 0.112256); // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key. - $$PREBID_GLOBAL$$._bidsReceived[0]['alwaysUseBid'] = true; - $$PREBID_GLOBAL$$._bidsReceived[0]['adserverTargeting'] = { + let _bidsReceived = getBidResponses(); + _bidsReceived[0]['adserverTargeting'] = { always_use_me: 'abc', }; + auction.getBidsReceived = function() { return _bidsReceived }; + var targeting = $$PREBID_GLOBAL$$.getAdserverTargeting(); // Ensure targeting for both ad placements includes the custom key. @@ -239,7 +258,7 @@ describe('Unit: Prebid Module', function () { var expected = { '/19968336/header-bid-tag-0': { - foobar: '300x250', + foobar: '300x250,300x600', hb_size: '300x250', hb_pb: '10.00', hb_adid: '233bcbee889d46d', @@ -254,16 +273,19 @@ describe('Unit: Prebid Module', function () { hb_bidder: 'appnexus' } }; - assert.deepEqual(targeting, expected); }); - it('should not overwrite winning bids custom keys targeting key when the bid has `alwaysUseBid` set to `true`', () => { + it('should not overwrite winning bids custom keys targeting key', () => { + resetAuction(); // mimic a bidderSetting.standard key here for each bid and alwaysUseBid true for every bid - $$PREBID_GLOBAL$$._bidsReceived.forEach(bid => { + let _bidsReceived = getBidResponses(); + _bidsReceived.forEach(bid => { bid.adserverTargeting.custom_ad_id = bid.adId; - bid.alwaysUseBid = true; }); + + auction.getBidsReceived = function() { return _bidsReceived }; + $$PREBID_GLOBAL$$.bidderSettings = { 'standard': { adserverTargeting: [{ @@ -316,26 +338,27 @@ describe('Unit: Prebid Module', function () { }); it('should not send standard targeting keys when the bid has `sendStandardTargeting` set to `false`', () => { - $$PREBID_GLOBAL$$._bidsReceived.forEach(bid => { + let _bidsReceived = getBidResponses(); + _bidsReceived.forEach(bid => { bid.adserverTargeting.custom_ad_id = bid.adId; bid.sendStandardTargeting = false; }); + auction.getBidsReceived = function() { return _bidsReceived }; + var targeting = $$PREBID_GLOBAL$$.getAdserverTargeting(); var expected = { '/19968336/header-bid-tag-0': { - foobar: '300x250', - custom_ad_id: '233bcbee889d46d' + foobar: '0x0,300x250,300x600', + custom_ad_id: '222bb26f9e8bd,233bcbee889d46d,25bedd4813632d7,26e0795ab963896,275bd666f5a5a5d,28f4039c636b6a7,29019e2ab586a5a' }, '/19968336/header-bid-tag1': { foobar: '728x90', custom_ad_id: '24bd938435ec3fc' } }; - assert.deepEqual(targeting, expected); - $$PREBID_GLOBAL$$.bidderSettings = {}; }); }); @@ -343,55 +366,145 @@ describe('Unit: Prebid Module', function () { const customConfigObject = { 'buckets': [ { 'precision': 2, 'min': 0, 'max': 5, 'increment': 0.01 }, - { 'precision': 2, 'min': 5, 'max': 8, 'increment': 0.05}, + { 'precision': 2, 'min': 5, 'max': 8, 'increment': 0.05 }, { 'precision': 2, 'min': 8, 'max': 20, 'increment': 0.5 }, { 'precision': 2, 'min': 20, 'max': 25, 'increment': 1 } ] }; let currentPriceBucket; let bid; + let auction; + let ajaxStub; + let cbTimeout = 3000; + let auctionManagerInstance = newAuctionManager(); + let targeting = newTargeting(auctionManagerInstance); + + let RESPONSE = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '4d0a6829338a07', + 'tag_id': 4799418, + 'auction_id': '2256922143947979797', + 'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad', + 'timeout_ms': 2500, + 'ads': [{ + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 958, + 'creative_id': 33989846, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 1.99, + 'cpm_publisher_currency': 0.500000, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'rtb': { + 'banner': { + 'width': 728, + 'height': 90, + 'content': '<!-- Creative -->' + }, + 'trackers': [{ + 'impression_urls': ['http://lax1-ib.adnxs.com/impression'] + }] + } + }] + }] + }; before(() => { - resetAuction(); + $$PREBID_GLOBAL$$.bidderSettings = {}; currentPriceBucket = configObj.getConfig('priceGranularity'); configObj.setConfig({ priceGranularity: customConfigObject }); - bid = Object.assign({}, - bidfactory.createBid(2), - getBidResponses()[5] - ); + sinon.stub(adaptermanager, 'makeBidRequests').callsFake(() => ([{ + 'bidderCode': 'appnexus', + 'auctionId': '20882439e3238c', + 'bidderRequestId': '331f3cf3f1d9c8', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '4d0a6829338a07', + 'bidderRequestId': '331f3cf3f1d9c8', + 'auctionId': '20882439e3238c' + } + ], + 'auctionStart': 1505250713622, + 'timeout': 3000 + }] + )); }); after(() => { configObj.setConfig({ priceGranularity: currentPriceBucket }); - resetAuction(); + adaptermanager.makeBidRequests.restore(); }) beforeEach(() => { - $$PREBID_GLOBAL$$._bidsReceived = []; - }) + let adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250], [300, 600]], + bids: [{ + bidder: 'appnexus', + params: { + placementId: '10433394' + } + }] + }]; + let adUnitCodes = ['div-gpt-ad-1460505748561-0']; + auction = auctionManagerInstance.createAuction({adUnits, adUnitCodes}); + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(function() { + return function(url, callback) { + const fakeResponse = sinon.stub(); + fakeResponse.returns('headerContent'); + callback.success(JSON.stringify(RESPONSE), { getResponseHeader: fakeResponse }); + } + }); + }); + + afterEach(() => { + ajaxStub.restore(); + }); it('should get correct hb_pb when using bid.cpm is between 0 to 5', () => { - bid.cpm = 2.1234; - bidmanager.addBidResponse(bid.adUnitCode, bid); - expect($$PREBID_GLOBAL$$.getAdserverTargeting()['/19968336/header-bid-tag-0'].hb_pb).to.equal('2.12'); + 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'); }); it('should get correct hb_pb when using bid.cpm is between 5 to 8', () => { - bid.cpm = 6.78; - bidmanager.addBidResponse(bid.adUnitCode, bid); - expect($$PREBID_GLOBAL$$.getAdserverTargeting()['/19968336/header-bid-tag-0'].hb_pb).to.equal('6.75'); + 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'); }); it('should get correct hb_pb when using bid.cpm is between 8 to 20', () => { - bid.cpm = 19.5234; - bidmanager.addBidResponse(bid.adUnitCode, bid); - expect($$PREBID_GLOBAL$$.getAdserverTargeting()['/19968336/header-bid-tag-0'].hb_pb).to.equal('19.50'); + 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'); }); it('should get correct hb_pb when using bid.cpm is between 20 to 25', () => { - bid.cpm = 21.5234; - bidmanager.addBidResponse(bid.adUnitCode, bid); - expect($$PREBID_GLOBAL$$.getAdserverTargeting()['/19968336/header-bid-tag-0'].hb_pb).to.equal('21.00'); + 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'); }); }); @@ -467,6 +580,7 @@ describe('Unit: Prebid Module', function () { it('should set targeting from googletag data', function () { var slots = createSlotArray(); + slots[0].spySetTargeting.resetHistory(); window.googletag.pubads().setSlots(slots); $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); @@ -487,17 +601,20 @@ describe('Unit: Prebid Module', function () { expect(slots[0].spySetTargeting.args).to.deep.contain.members(expected); }); - it('should set targeting for bids with `alwaysUseBid=true`', function () { + it('should set targeting for bids', function () { // Make sure we're getting the expected losing bid. - assert.equal($$PREBID_GLOBAL$$._bidsReceived[0]['bidderCode'], 'triplelift'); - assert.equal($$PREBID_GLOBAL$$._bidsReceived[0]['cpm'], 0.112256); + assert.equal(auctionManager.getBidsReceived()[0]['bidderCode'], 'triplelift'); + assert.equal(auctionManager.getBidsReceived()[0]['cpm'], 0.112256); + resetAuction(); // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key. - $$PREBID_GLOBAL$$._bidsReceived[0]['alwaysUseBid'] = true; - $$PREBID_GLOBAL$$._bidsReceived[0]['adserverTargeting'] = { + let _bidsReceived = getBidResponses(); + _bidsReceived[0]['adserverTargeting'] = { always_use_me: 'abc', }; + auction.getBidsReceived = function() { return _bidsReceived }; + var slots = createSlotArray(); window.googletag.pubads().setSlots(slots); @@ -522,15 +639,11 @@ describe('Unit: Prebid Module', function () { ], [ 'foobar', - '300x250' + ['300x250', '300x600'] ], [ 'always_use_me', 'abc' - ], - [ - 'foobar', - '300x250' ] ]; @@ -560,16 +673,6 @@ describe('Unit: Prebid Module', function () { }); }); - describe('allBidsAvailable', function () { - it('should call bidmanager.allBidsBack', function () { - var spyAllBidsBack = sinon.spy(bidmanager, 'bidsBackAll'); - - $$PREBID_GLOBAL$$.allBidsAvailable(); - assert.ok(spyAllBidsBack.called, 'called bidmanager.allBidsBack'); - bidmanager.bidsBackAll.restore(); - }); - }); - describe('renderAd', function () { var bidId = 1; var doc = {}; @@ -578,6 +681,22 @@ describe('Unit: Prebid Module', function () { var spyLogError = null; var spyLogMessage = null; var inIframe = true; + let triggerPixelStub; + + function pushBidResponseToAuction(obj) { + adResponse = Object.assign({ + auctionId: 1, + adId: bidId, + width: 300, + height: 250, + }, obj); + auction.getBidsReceived = function() { + let bidsReceived = getBidResponses(); + bidsReceived.push(adResponse); + return bidsReceived; + } + auction.getAuctionId = () => 1; + } beforeEach(function () { doc = { @@ -597,26 +716,20 @@ describe('Unit: Prebid Module', function () { }; doc.getElementsByTagName.returns([elStub]); - adResponse = { - adId: bidId, - width: 300, - height: 250, - }; - $$PREBID_GLOBAL$$._bidsReceived.push(adResponse); - spyLogError = sinon.spy(utils, 'logError'); spyLogMessage = sinon.spy(utils, 'logMessage'); inIframe = true; - sinon.stub(utils, 'inIframe', () => inIframe); + sinon.stub(utils, 'inIframe').callsFake(() => inIframe); + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); }); afterEach(function () { - $$PREBID_GLOBAL$$._bidsReceived.splice($$PREBID_GLOBAL$$._bidsReceived.indexOf(adResponse), 1); - $$PREBID_GLOBAL$$._winningBids = []; + auction.getBidsReceived = getBidResponses; utils.logError.restore(); utils.logMessage.restore(); utils.inIframe.restore(); + utils.triggerPixel.restore(); }); it('should require doc and id params', function () { @@ -632,6 +745,9 @@ describe('Unit: Prebid Module', function () { }); it('should write the ad to the doc', function () { + pushBidResponseToAuction({ + ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>" + }); adResponse.ad = "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>"; $$PREBID_GLOBAL$$.renderAd(doc, bidId); assert.ok(doc.write.calledWith(adResponse.ad), 'ad was written to doc'); @@ -639,18 +755,24 @@ describe('Unit: Prebid Module', function () { }); it('should place the url inside an iframe on the doc', function () { - adResponse.adUrl = 'http://server.example.com/ad/ad.js'; + pushBidResponseToAuction({ + adUrl: 'http://server.example.com/ad/ad.js' + }); $$PREBID_GLOBAL$$.renderAd(doc, bidId); assert.ok(elStub.insertBefore.called, 'url was written to iframe in doc'); }); it('should log an error when no ad or url', function () { + pushBidResponseToAuction({}); $$PREBID_GLOBAL$$.renderAd(doc, bidId); var error = 'Error trying to write ad. No ad for bid response id: ' + bidId; assert.ok(spyLogError.calledWith(error), 'expected error was logged'); }); it('should log an error when not in an iFrame', () => { + pushBidResponseToAuction({ + ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>" + }); inIframe = false; $$PREBID_GLOBAL$$.renderAd(document, bidId); const error = 'Error trying to write ad. Ad render call ad id ' + bidId + ' was prevented from writing to the main document.'; @@ -658,14 +780,17 @@ describe('Unit: Prebid Module', function () { }); it('should not render videos', () => { - adResponse.mediatype = 'video'; + pushBidResponseToAuction({ + mediatype: 'video' + }); $$PREBID_GLOBAL$$.renderAd(doc, bidId); sinon.assert.notCalled(doc.write); - delete adResponse.mediatype; }); it('should catch errors thrown when trying to write ads to the page', function () { - adResponse.ad = "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>"; + pushBidResponseToAuction({ + ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>" + }); var error = { message: 'doc write error' }; doc.write = sinon.stub().throws(error); @@ -683,385 +808,450 @@ describe('Unit: Prebid Module', function () { }); it('should save bid displayed to winning bid', function () { + pushBidResponseToAuction({ + ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>" + }); $$PREBID_GLOBAL$$.renderAd(doc, bidId); - assert.equal($$PREBID_GLOBAL$$._winningBids[0], adResponse); + assert.deepEqual($$PREBID_GLOBAL$$.getAllWinningBids()[0], adResponse); }); - }); - describe('requestBids', () => { - var adUnitsBackup; + it('fires billing url if present on s2s bid', () => { + const burl = 'http://www.example.com/burl'; + pushBidResponseToAuction({ + ad: '<div>ad</div>', + source: 's2s', + burl + }); - beforeEach(() => { - adUnitsBackup = $$PREBID_GLOBAL$$.adUnits; - }); + $$PREBID_GLOBAL$$.renderAd(doc, bidId); - afterEach(() => { - $$PREBID_GLOBAL$$.adUnits = adUnitsBackup; - resetAuction(); + sinon.assert.calledOnce(triggerPixelStub); + sinon.assert.calledWith(triggerPixelStub, burl); }); + }); - it('should add bidsBackHandler callback to bidmanager', () => { - var spyAddOneTimeCallBack = sinon.spy(bidmanager, 'addOneTimeCallback'); - var requestObj = { - bidsBackHandler: function bidsBackHandlerCallback() { + describe('requestBids', () => { + let logMessageSpy; + let makeRequestsStub; + let xhr; + let adUnits; + let clock; + + const BIDDER_CODE = 'sampleBidder'; + let bids = [{ + 'ad': 'creative', + 'cpm': '1.99', + 'width': 300, + 'height': 250, + 'bidderCode': BIDDER_CODE, + 'requestId': '4d0a6829338a07', + 'creativeId': 'id', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360 + }]; + let bidRequests = [{ + 'bidderCode': BIDDER_CODE, + 'auctionId': '20882439e3238c', + 'bidderRequestId': '331f3cf3f1d9c8', + 'bids': [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'placementId': 'id' + }, + 'adUnitCode': 'adUnit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4d0a6829338a07', + 'bidderRequestId': '331f3cf3f1d9c8', + 'auctionId': '20882439e3238c' } - }; - $$PREBID_GLOBAL$$.requestBids(requestObj); - assert.ok(spyAddOneTimeCallBack.calledWith(requestObj.bidsBackHandler), - 'called bidmanager.addOneTimeCallback'); - bidmanager.addOneTimeCallback.restore(); - }); + ], + 'auctionStart': 1505250713622, + 'timeout': 3000, + 'start': 1000 + }]; - it('should log message when adUnits not configured', () => { - const logMessageSpy = sinon.spy(utils, 'logMessage'); + beforeEach(() => { + logMessageSpy = sinon.spy(utils, 'logMessage'); + makeRequestsStub = sinon.stub(adaptermanager, 'makeBidRequests'); + makeRequestsStub.returns(bidRequests); + xhr = sinon.useFakeXMLHttpRequest(); - $$PREBID_GLOBAL$$.adUnits = []; - $$PREBID_GLOBAL$$.requestBids({}); + adUnits = [{ + code: 'adUnit-code', + bids: [ + {bidder: BIDDER_CODE, params: {placementId: 'id'}}, + ] + }]; + let adUnitCodes = ['adUnit-code']; + let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 2000}); + let createAuctionStub = sinon.stub(auctionModule, 'newAuction'); + createAuctionStub.returns(auction); + }); - assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged'); + afterEach(() => { + clock.restore(); + adaptermanager.makeBidRequests.restore(); + auctionModule.newAuction.restore(); utils.logMessage.restore(); + xhr.restore(); }); it('should execute callback after timeout', () => { - var spyExecuteCallback = sinon.spy(bidmanager, 'executeCallback'); - var clock = sinon.useFakeTimers(); - var requestObj = { - bidsBackHandler: function bidsBackHandlerCallback() { - }, + let spec = { + code: BIDDER_CODE, + isBidRequestValid: sinon.stub(), + buildRequests: sinon.stub(), + interpretResponse: sinon.stub(), + getUserSyncs: sinon.stub() + }; - timeout: 2000 + registerBidder(spec); + spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); + spec.isBidRequestValid.returns(true); + spec.interpretResponse.returns(bids); + + clock = sinon.useFakeTimers(); + let requestObj = { + bidsBackHandler: function bidsBackHandlerCallback() {}, + timeout: 2000, + adUnits: adUnits }; $$PREBID_GLOBAL$$.requestBids(requestObj); - + let re = new RegExp('^Auction [a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12} timedOut$'); clock.tick(requestObj.timeout - 1); - assert.ok(spyExecuteCallback.notCalled, 'bidmanager.executeCallback not called'); + assert.ok(logMessageSpy.neverCalledWith(sinon.match(re)), 'executeCallback not called'); clock.tick(1); - assert.ok(spyExecuteCallback.called, 'called bidmanager.executeCallback'); - - bidmanager.executeCallback.restore(); - clock.restore(); + assert.ok(logMessageSpy.calledWith(sinon.match(re)), 'executeCallback called'); }); + }) - it('should execute callback immediately if adUnits is empty', () => { - var spyExecuteCallback = sinon.spy(bidmanager, 'executeCallback'); - - $$PREBID_GLOBAL$$.adUnits = []; - $$PREBID_GLOBAL$$.requestBids({}); - - assert.ok(spyExecuteCallback.calledOnce, 'callback executed immediately when adUnits is' + - ' empty'); + describe('requestBids', () => { + let xhr; + let requests; - bidmanager.executeCallback.restore(); + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); }); - it('should not propagate exceptions from bidsBackHandler', () => { - $$PREBID_GLOBAL$$.adUnits = []; + afterEach(() => xhr.restore()); + var adUnitsBackup; + var auctionManagerStub; + let logMessageSpy + + let spec = { + code: 'sampleBidder', + isBidRequestValid: () => {}, + buildRequests: () => {}, + interpretResponse: () => {}, + getUserSyncs: () => {} + }; + registerBidder(spec); + + describe('part 1', () => { + beforeEach(() => { + adUnitsBackup = auction.getAdUnits + auctionManagerStub = sinon.stub(auctionManager, 'createAuction').callsFake(function() { + return auction; + }); + logMessageSpy = sinon.spy(utils, 'logMessage'); + }); + + afterEach(() => { + auction.getAdUnits = adUnitsBackup; + auctionManager.createAuction.restore(); + utils.logMessage.restore(); + resetAuction(); + }); - var requestObj = { - bidsBackHandler: function bidsBackHandlerCallback() { - var test; - return test.test; + it('should log message when adUnits not configured', () => { + $$PREBID_GLOBAL$$.adUnits = []; + try { + $$PREBID_GLOBAL$$.requestBids({}); + } catch (e) { + console.log(e); } - }; + assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged'); + }); - expect(() => { - $$PREBID_GLOBAL$$.requestBids(requestObj); - }).not.to.throw(); - }); + it('should execute callback immediately if adUnits is empty', () => { + var bidsBackHandler = function bidsBackHandlerCallback() {}; + var spyExecuteCallback = sinon.spy(bidsBackHandler); - it('should call callBids function on adaptermanager', () => { - var spyCallBids = sinon.spy(adaptermanager, 'callBids'); + $$PREBID_GLOBAL$$.adUnits = []; + $$PREBID_GLOBAL$$.requestBids({ + bidsBackHandler: spyExecuteCallback + }); - $$PREBID_GLOBAL$$.requestBids({}); - assert.ok(spyCallBids.called, 'called adaptermanager.callBids'); - adaptermanager.callBids.restore(); - }); + assert.ok(spyExecuteCallback.calledOnce, 'callback executed immediately when adUnits is' + + ' empty'); + }); - it('should not callBids if a video adUnit has non-video bidders', () => { - sinon.spy(adaptermanager, 'callBids'); - const videoAdaptersBackup = adaptermanager.videoAdapters; - adaptermanager.videoAdapters = ['appnexusAst']; - const adUnits = [{ - code: 'adUnit-code', - mediaType: 'video', - bids: [ - {bidder: 'appnexus', params: {placementId: 'id'}}, - {bidder: 'appnexusAst', params: {placementId: 'id'}} - ] - }]; + it('should not propagate exceptions from bidsBackHandler', () => { + $$PREBID_GLOBAL$$.adUnits = []; - $$PREBID_GLOBAL$$.requestBids({adUnits}); - sinon.assert.notCalled(adaptermanager.callBids); + var requestObj = { + bidsBackHandler: function bidsBackHandlerCallback() { + var test; + return test.test; + } + }; - adaptermanager.callBids.restore(); - adaptermanager.videoAdapters = videoAdaptersBackup; + expect(() => { + $$PREBID_GLOBAL$$.requestBids(requestObj); + }).not.to.throw(); + }); }); - it('should callBids if a video adUnit has all video bidders', () => { - sinon.spy(adaptermanager, 'callBids'); - const videoAdaptersBackup = adaptermanager.videoAdapters; - adaptermanager.videoAdapters = ['appnexusAst']; - const adUnits = [{ - code: 'adUnit-code', - mediaType: 'video', - bids: [ - {bidder: 'appnexusAst', params: {placementId: 'id'}} - ] - }]; + describe('multiformat requests', () => { + let spyCallBids; + let createAuctionStub; + let adUnits; + + beforeEach(() => { + adUnits = [{ + code: 'adUnit-code', + mediaTypes: { + banner: {}, + native: {}, + }, + sizes: [[300, 250], [300, 600]], + bids: [ + {bidder: 'appnexus', params: {placementId: 'id'}}, + {bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}} + ] + }]; + adUnitCodes = ['adUnit-code']; + let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); + spyCallBids = sinon.spy(adaptermanager, 'callBids'); + createAuctionStub = sinon.stub(auctionModule, 'newAuction'); + createAuctionStub.returns(auction); + }) + + afterEach(() => { + auctionModule.newAuction.restore(); + adaptermanager.callBids.restore(); + }); - $$PREBID_GLOBAL$$.requestBids({adUnits}); - sinon.assert.calledOnce(adaptermanager.callBids); + it('bidders that support one of the declared formats are allowed to participate', () => { + $$PREBID_GLOBAL$$.requestBids({adUnits}); + sinon.assert.calledOnce(adaptermanager.callBids); - adaptermanager.callBids.restore(); - adaptermanager.videoAdapters = videoAdaptersBackup; - }); + const spyArgs = adaptermanager.callBids.getCall(0); + const biddersCalled = spyArgs.args[0][0].bids; - it('should only request native bidders on native adunits', () => { - sinon.spy(adaptermanager, 'callBids'); - // appnexusAst is a native bidder, appnexus is not - const adUnits = [{ - code: 'adUnit-code', - mediaType: 'native', - bids: [ - {bidder: 'appnexus', params: {placementId: 'id'}}, - {bidder: 'appnexusAst', params: {placementId: 'id'}} - ] - }]; + // appnexus and sampleBidder both support banner + expect(biddersCalled.length).to.equal(2); + }); + + it('bidders that do not support one of the declared formats are dropped', () => { + delete adUnits[0].mediaTypes.banner; - $$PREBID_GLOBAL$$.requestBids({adUnits}); - sinon.assert.calledOnce(adaptermanager.callBids); + $$PREBID_GLOBAL$$.requestBids({adUnits}); + sinon.assert.calledOnce(adaptermanager.callBids); - const spyArgs = adaptermanager.callBids.getCall(0); - const biddersCalled = spyArgs.args[0].adUnits[0].bids; - expect(biddersCalled.length).to.equal(1); + const spyArgs = adaptermanager.callBids.getCall(0); + const biddersCalled = spyArgs.args[0][0].bids; - adaptermanager.callBids.restore(); + // only appnexus supports native + expect(biddersCalled.length).to.equal(1); + }); }); - it('should callBids if a native adUnit has all native bidders', () => { - sinon.spy(adaptermanager, 'callBids'); - // TODO: appnexusAst is currently hardcoded in native.js, update this text when fixed - const adUnits = [{ - code: 'adUnit-code', - mediaType: 'native', - bids: [ - {bidder: 'appnexusAst', params: {placementId: 'id'}} - ] - }]; + describe('part 2', () => { + let spyCallBids; + let createAuctionStub; + let adUnits; - $$PREBID_GLOBAL$$.requestBids({adUnits}); - sinon.assert.calledOnce(adaptermanager.callBids); + before(() => { + adUnits = [{ + code: 'adUnit-code', + sizes: [[300, 250], [300, 600]], + bids: [ + {bidder: 'appnexus', params: {placementId: '10433394'}} + ] + }]; + let adUnitCodes = ['adUnit-code']; + let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); + + adUnits[0]['mediaType'] = 'native'; + adUnitCodes = ['adUnit-code']; + let auction1 = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); + + adUnits = [{ + code: 'adUnit-code', + nativeParams: {type: 'image'}, + sizes: [[300, 250], [300, 600]], + bids: [ + {bidder: 'appnexus', params: {placementId: 'id'}} + ] + }]; + let auction3 = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); - adaptermanager.callBids.restore(); - }); + let createAuctionStub = sinon.stub(auctionModule, 'newAuction'); + createAuctionStub.onCall(0).returns(auction1); + createAuctionStub.onCall(2).returns(auction3); + createAuctionStub.returns(auction); + }); - it('splits native type to individual native assets', () => { - $$PREBID_GLOBAL$$._bidsRequested = []; + after(() => { + auctionModule.newAuction.restore(); + }); - const adUnits = [{ - code: 'adUnit-code', - nativeParams: {type: 'image'}, - bids: [ - {bidder: 'appnexusAst', params: {placementId: 'id'}} - ] - }]; + beforeEach(() => { + spyCallBids = sinon.spy(adaptermanager, 'callBids'); + }) - $$PREBID_GLOBAL$$.requestBids({adUnits}); + afterEach(() => { + adaptermanager.callBids.restore(); + }) - const nativeRequest = $$PREBID_GLOBAL$$._bidsRequested[0].bids[0].nativeParams; - expect(nativeRequest).to.deep.equal({ - image: {required: true}, - title: {required: true}, - sponsoredBy: {required: true}, - clickUrl: {required: true}, - body: {required: false}, - icon: {required: false}, + it('should callBids if a native adUnit has all native bidders', () => { + $$PREBID_GLOBAL$$.requestBids({adUnits}); + sinon.assert.calledOnce(adaptermanager.callBids); }); - resetAuction(); + it('should call callBids function on adaptermanager', () => { + let adUnits = [{ + code: 'adUnit-code', + sizes: [[300, 250], [300, 600]], + bids: [ + {bidder: 'appnexus', params: {placementId: '10433394'}} + ] + }]; + $$PREBID_GLOBAL$$.requestBids({adUnits}); + assert.ok(spyCallBids.called, 'called adaptermanager.callBids'); + }); + + it('splits native type to individual native assets', () => { + let adUnits = [{ + code: 'adUnit-code', + nativeParams: {type: 'image'}, + sizes: [[300, 250], [300, 600]], + bids: [ + {bidder: 'appnexus', params: {placementId: 'id'}} + ] + }]; + $$PREBID_GLOBAL$$.requestBids({adUnits}); + const spyArgs = adaptermanager.callBids.getCall(0); + const nativeRequest = spyArgs.args[1][0].bids[0].nativeParams; + expect(nativeRequest).to.deep.equal({ + image: {required: true}, + title: {required: true}, + sponsoredBy: {required: true}, + clickUrl: {required: true}, + body: {required: false}, + icon: {required: false}, + }); + resetAuction(); + }); }); - it('should queue bid requests when a previous bid request is in process', () => { - var spyCallBids = sinon.spy(adaptermanager, 'callBids'); - var clock = sinon.useFakeTimers(); - var requestObj1 = { - adUnitCodes: ['/19968336/header-bid-tag1'], - bidsBackHandler: function bidsBackHandlerCallback() { - }, + describe('part-3', () => { + let auctionManagerInstance = newAuctionManager(); + let auctionManagerStub; + let adUnits1 = getAdUnits().filter((adUnit) => { + return adUnit.code === '/19968336/header-bid-tag1'; + }); + let adUnitCodes1 = getAdUnits().map(unit => unit.code); + let auction1 = auctionManagerInstance.createAuction({adUnits: adUnits1, adUnitCodes: adUnitCodes1}); - timeout: 2000 + let adUnits2 = getAdUnits().filter((adUnit) => { + return adUnit.code === '/19968336/header-bid-tag-0'; + }); + let adUnitCodes2 = getAdUnits().map(unit => unit.code); + let auction2 = auctionManagerInstance.createAuction({adUnits: adUnits2, adUnitCodes: adUnitCodes2}); + let spyCallBids; + + auction1.getBidRequests = function() { + return getBidRequests().map((req) => { + req.bids = req.bids.filter((bid) => { + return bid.adUnitCode === '/19968336/header-bid-tag1'; + }); + return (req.bids.length > 0) ? req : undefined; + }).filter((item) => { + return item != undefined; + }); + }; + auction1.getBidsReceived = function() { + return getBidResponses().filter((bid) => { + return bid.adUnitCode === '/19968336/header-bid-tag1'; + }); }; - var requestObj2 = { - adUnitCodes: ['/19968336/header-bid-tag-0'], - bidsBackHandler: function bidsBackHandlerCallback() { - }, - - timeout: 2000 + auction2.getBidRequests = function() { + return getBidRequests().map((req) => { + req.bids = req.bids.filter((bid) => { + return bid.adUnitCode === '/19968336/header-bid-tag-0'; + }); + return (req.bids.length > 0) ? req : undefined; + }).filter((item) => { + return item != undefined; + }); + }; + auction2.getBidsReceived = function() { + return getBidResponses().filter((bid) => { + return bid.adUnitCode === '/19968336/header-bid-tag-0'; + }); }; - assert.equal($$PREBID_GLOBAL$$._bidsReceived.length, 8, '_bidsReceived contains 8 bids'); - - $$PREBID_GLOBAL$$.requestBids(requestObj1); - $$PREBID_GLOBAL$$.requestBids(requestObj2); - - clock.tick(requestObj1.timeout - 1); - assert.ok(spyCallBids.calledOnce, 'When two requests for bids are made only one should' + - ' callBids immediately'); - assert.equal($$PREBID_GLOBAL$$._bidsReceived.length, 7, '_bidsReceived now contains 7 bids'); - assert.deepEqual($$PREBID_GLOBAL$$._bidsReceived - .find(bid => requestObj1.adUnitCodes.includes(bid.adUnitCode)), undefined, 'Placements' + - ' for' + - ' current request have been cleared of bids'); - assert.deepEqual($$PREBID_GLOBAL$$._bidsReceived - .filter(bid => requestObj2.adUnitCodes.includes(bid.adUnitCode)).length, 7, 'Placements' + - ' for previous request have not been cleared of bids'); - assert.deepEqual($$PREBID_GLOBAL$$._adUnitCodes, ['/19968336/header-bid-tag1'], '_adUnitCodes is' + - ' for first request'); - assert.ok($$PREBID_GLOBAL$$._bidsReceived.length > 0, '_bidsReceived contains bids'); - assert.deepEqual($$PREBID_GLOBAL$$.getBidResponses(), {}, 'yet getBidResponses returns' + - ' empty object for first request (no matching bids for current placement'); - assert.deepEqual($$PREBID_GLOBAL$$.getAdserverTargeting(), {}, 'getAdserverTargeting' + - ' returns empty object for first request'); - clock.tick(1); + beforeEach(function() { + spyCallBids = sinon.spy(adaptermanager, 'callBids'); + auctionManagerStub = sinon.stub(auctionManager, 'createAuction'); + auctionManagerStub.onCall(0).returns(auction1); + auctionManagerStub.onCall(1).returns(auction2); + }); - // restore _bidsReceived to simulate more bids returned - $$PREBID_GLOBAL$$._bidsReceived = getBidResponses(); - assert.ok(spyCallBids.calledTwice, 'The second queued request should callBids when the' + - ' first request has completed'); - assert.deepEqual($$PREBID_GLOBAL$$._adUnitCodes, ['/19968336/header-bid-tag-0'], '_adUnitCodes is' + - 'now for second request'); - assert.deepEqual($$PREBID_GLOBAL$$.getBidResponses(), { - '/19968336/header-bid-tag-0': { - 'bids': [ - { - 'bidderCode': 'brightcom', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '26e0795ab963896', - 'cpm': 0.17, - 'ad': "<script type=\"text/javascript\">document.write('<scr'+'ipt src=\"//trk.diamondminebubble.com/h.html?e=hb_before_creative_renders&ho=2140340&ty=j&si=300x250&ta=16577&cd=cdn.marphezis.com&raid=15f3d12e77c1e5a&rimid=14fe662ee0a3506&rbid=235894352&cb=' + Math.floor((Math.random()*100000000000)+1) + '&ref=\"></scr' + 'ipt>');</script><script type=\"text/javascript\">var compassSmartTag={h:\"2140340\",t:\"16577\",d:\"2\",referral:\"\",y_b:{y:\"j\",s:\"300x250\"},hb:{raid:\"15f3d12e77c1e5a\",rimid:\"14fe662ee0a3506\",rbid:\"235894352\"}};</script><script src=\"//cdn.marphezis.com/cmps/cst.min.js\"></script><img src=\"http://notifications.iselephant.com/hb/awin?byid=400&imid=14fe662ee0a3506&auid=15f3d12e77c1e5a&bdid=235894352\" width=\"1\" height=\"1\" style=\"display:none\" />", - 'responseTimestamp': 1462919239420, - 'requestTimestamp': 1462919238937, - 'bidder': 'brightcom', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'timeToRespond': 483, - 'pbLg': '0.00', - 'pbMg': '0.10', - 'pbHg': '0.17', - 'pbAg': '0.15', - 'size': '300x250', - 'requestId': 654321, - 'adserverTargeting': { - 'hb_bidder': 'brightcom', - 'hb_adid': '26e0795ab963896', - 'hb_pb': '10.00', - 'hb_size': '300x250', - 'foobar': '300x250' - } - }, - { - 'bidderCode': 'brealtime', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '275bd666f5a5a5d', - 'creative_id': 29681110, - 'cpm': 0.5, - 'adUrl': 'http://lax1-ib.adnxs.com/ab?e=wqT_3QLzBKhzAgAAAwDWAAUBCMjAybkFEIPr4YfMvKLoQBjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbLkw5YweAnYABokUB4mo8EgAEBigEDVVNEkgUG8FKYAawCoAH6AagBAbABALgBAcABA8gBANABANgBAOABAPABAIoCOnVmKCdhJywgNDk0NDcyLCAxNDYyOTE5MjQwKTt1ZigncicsIDI5NjgxMTEwLDIeAPBvkgLNASFsU2NQWlFpNjBJY0VFTmJMa3c0WUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCdzNBMTRDb0FCcGh5SUFRcVFBUUdZQVFHZ0FRR29BUU93QVFDNUFRQUFBQUFBQU9BX3dRRQkMSEFEZ1A4a0JHZmNvazFBejFUX1oVKCRQQV80QUVBOVFFBSw8bUFLS2dOU0NEYUFDQUxVQwUVBEwwCQh0T0FDQU9nQ0FQZ0NBSUFEQVEuLpoCJSFDUWxfYXdpMtAA8KZ3ZUFuSUFRb2lvRFVnZzAu2ALoB-ACx9MB6gIfaHR0cDovL3ByZWJpZC5vcmc6OTk5OS9ncHQuaHRtbIADAIgDAZADAJgDBaADAaoDALADALgDAMADrALIAwDYAwDgAwDoAwD4AwOABACSBAQvanB0mAQAogQKMTAuMS4xMy4zN6gEi-wJsgQICAAQABgAIAC4BADABADIBADSBAsxMC4wLjg1LjIwOA..&s=975cfe6518f064683541240f0d780d93a5f973da&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html', - 'responseTimestamp': 1462919239486, - 'requestTimestamp': 1462919238941, - 'bidder': 'brealtime', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'timeToRespond': 545, - 'pbLg': '0.50', - 'pbMg': '0.50', - 'pbHg': '0.50', - 'pbAg': '0.50', - 'size': '300x250', - 'requestId': 654321, - 'adserverTargeting': { - 'hb_bidder': 'brealtime', - 'hb_adid': '275bd666f5a5a5d', - 'hb_pb': '10.00', - 'hb_size': '300x250', - 'foobar': '300x250' - } - }, - { - 'bidderCode': 'pubmatic', - 'width': '300', - 'height': '250', - 'statusMessage': 'Bid available', - 'adId': '28f4039c636b6a7', - 'adSlot': '39620189@300x250', - 'cpm': 5.9396, - 'ad': "<span class=\"PubAPIAd\"><img src=\"http://usw-lax.adsrvr.org/bid/feedback/pubmatic?iid=467b5d95-d55a-4125-a90a-64a34d92ceec&crid=p84y3ree&wp=8.5059874&aid=9519B012-A2CF-4166-93F5-DEB9D7CC9680&wpc=USD&sfe=969e047&puid=4367D163-7DC9-40CD-8DC1-0A0876574ADE&tdid=9514a176-457b-4bb1-ae75-0d2b5e8012fa&pid=rw83mt1&ag=rmorau3&cf=&fq=1&td_s=prebid.org:9999&rcats=&mcat=&mste=&mfld=2&mssi=&mfsi=s4go1cqvhn&uhow=63&agsa=&rgco=United%20States&rgre=Oregon&rgme=820&rgci=Portland&rgz=97204&svbttd=1&dt=PC&osf=OSX&os=Other&br=Chrome&rlangs=en&mlang=&svpid=39741&did=&rcxt=Other&lat=45.518097&lon=-122.675095&tmpc=&daid=&vp=0&osi=&osv=&bp=13.6497&testid=audience-eval-old&dur=CicKB203c2NmY3oQhJUDIgsIncWDPRIEbm9uZSILCOjyjz0SBG5vbmUKNQoeY2hhcmdlLWFsbFBlZXIzOUN1c3RvbUNhdGVnb3J5IhMI/f//////////ARIGcGVlcjM5EISVAw==&crrelr=\" width=\"1\" height=\"1\" style=\"display: none;\"/><IFRAME SRC=\"https://ad.doubleclick.net/ddm/adi/N84001.284566THETRADEDESK/B9241716.125553599;sz=300x250;click0=http://insight.adsrvr.org/track/clk?imp=467b5d95-d55a-4125-a90a-64a34d92ceec&ag=rmorau3&crid=p84y3ree&cf=&fq=1&td_s=prebid.org:9999&rcats=&mcat=&mste=&mfld=2&mssi=&mfsi=s4go1cqvhn&sv=pubmatic&uhow=63&agsa=&rgco=United%20States&rgre=Oregon&rgme=820&rgci=Portland&rgz=97204&dt=PC&osf=OSX&os=Other&br=Chrome&svpid=39741&rlangs=en&mlang=&did=&rcxt=Other&tmpc=&vrtd=&osi=&osv=&daid=&dnr=0&dur=CicKB203c2NmY3oQhJUDIgsIncWDPRIEbm9uZSILCOjyjz0SBG5vbmUKNQoeY2hhcmdlLWFsbFBlZXIzOUN1c3RvbUNhdGVnb3J5IhMI%2Ff%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARIGcGVlcjM5EISVAw%3D%3D&crrelr=&svscid=66156&testid=audience-eval-old&r=;ord=102917?\" WIDTH=300 HEIGHT=250 MARGINWIDTH=0 MARGINHEIGHT=0 HSPACE=0 VSPACE=0 FRAMEBORDER=0 SCROLLING=no BORDERCOLOR='#000000'>\r\n<SCRIPT language='JavaScript1.1' SRC=\"https://ad.doubleclick.net/ddm/adj/N84001.284566THETRADEDESK/B9241716.125553599;abr=!ie;sz=300x250;click0=http://insight.adsrvr.org/track/clk?imp=467b5d95-d55a-4125-a90a-64a34d92ceec&ag=rmorau3&crid=p84y3ree&cf=&fq=1&td_s=prebid.org:9999&rcats=&mcat=&mste=&mfld=2&mssi=&mfsi=s4go1cqvhn&sv=pubmatic&uhow=63&agsa=&rgco=United%20States&rgre=Oregon&rgme=820&rgci=Portland&rgz=97204&dt=PC&osf=OSX&os=Other&br=Chrome&svpid=39741&rlangs=en&mlang=&did=&rcxt=Other&tmpc=&vrtd=&osi=&osv=&daid=&dnr=0&dur=CicKB203c2NmY3oQhJUDIgsIncWDPRIEbm9uZSILCOjyjz0SBG5vbmUKNQoeY2hhcmdlLWFsbFBlZXIzOUN1c3RvbUNhdGVnb3J5IhMI%2Ff%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARIGcGVlcjM5EISVAw%3D%3D&crrelr=&svscid=66156&testid=audience-eval-old&r=;ord=102917?\">\r\n</SCRIPT>\r\n<NOSCRIPT>\r\n<A HREF=\"http://insight.adsrvr.org/track/clk?imp=467b5d95-d55a-4125-a90a-64a34d92ceec&ag=rmorau3&crid=p84y3ree&cf=&fq=1&td_s=prebid.org:9999&rcats=&mcat=&mste=&mfld=2&mssi=&mfsi=s4go1cqvhn&sv=pubmatic&uhow=63&agsa=&rgco=United%20States&rgre=Oregon&rgme=820&rgci=Portland&rgz=97204&dt=PC&osf=OSX&os=Other&br=Chrome&svpid=39741&rlangs=en&mlang=&did=&rcxt=Other&tmpc=&vrtd=&osi=&osv=&daid=&dnr=0&dur=CicKB203c2NmY3oQhJUDIgsIncWDPRIEbm9uZSILCOjyjz0SBG5vbmUKNQoeY2hhcmdlLWFsbFBlZXIzOUN1c3RvbUNhdGVnb3J5IhMI%2Ff%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARIGcGVlcjM5EISVAw%3D%3D&crrelr=&svscid=66156&testid=audience-eval-old&r=https://ad.doubleclick.net/ddm/jump/N84001.284566THETRADEDESK/B9241716.125553599;abr=!ie4;abr=!ie5;sz=300x250;click0=http://insight.adsrvr.org/track/clk?imp=467b5d95-d55a-4125-a90a-64a34d92ceec&ag=rmorau3&crid=p84y3ree&cf=&fq=1&td_s=prebid.org:9999&rcats=&mcat=&mste=&mfld=2&mssi=&mfsi=s4go1cqvhn&sv=pubmatic&uhow=63&agsa=&rgco=United%20States&rgre=Oregon&rgme=820&rgci=Portland&rgz=97204&dt=PC&osf=OSX&os=Other&br=Chrome&svpid=39741&rlangs=en&mlang=&did=&rcxt=Other&tmpc=&vrtd=&osi=&osv=&daid=&dnr=0&dur=CicKB203c2NmY3oQhJUDIgsIncWDPRIEbm9uZSILCOjyjz0SBG5vbmUKNQoeY2hhcmdlLWFsbFBlZXIzOUN1c3RvbUNhdGVnb3J5IhMI%2Ff%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARIGcGVlcjM5EISVAw%3D%3D&crrelr=&svscid=66156&testid=audience-eval-old&r=;ord=102917?\">\r\n<IMG SRC=\"https://ad.doubleclick.net/ddm/ad/N84001.284566THETRADEDESK/B9241716.125553599;abr=!ie4;abr=!ie5;sz=300x250;click0=http://insight.adsrvr.org/track/clk?imp=467b5d95-d55a-4125-a90a-64a34d92ceec&ag=rmorau3&crid=p84y3ree&cf=&fq=1&td_s=prebid.org:9999&rcats=&mcat=&mste=&mfld=2&mssi=&mfsi=s4go1cqvhn&sv=pubmatic&uhow=63&agsa=&rgco=United%20States&rgre=Oregon&rgme=820&rgci=Portland&rgz=97204&dt=PC&osf=OSX&os=Other&br=Chrome&svpid=39741&rlangs=en&mlang=&did=&rcxt=Other&tmpc=&vrtd=&osi=&osv=&daid=&dnr=0&dur=CicKB203c2NmY3oQhJUDIgsIncWDPRIEbm9uZSILCOjyjz0SBG5vbmUKNQoeY2hhcmdlLWFsbFBlZXIzOUN1c3RvbUNhdGVnb3J5IhMI%2Ff%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARIGcGVlcjM5EISVAw%3D%3D&crrelr=&svscid=66156&testid=audience-eval-old&r=;ord=102917?\" BORDER=0 WIDTH=300 HEIGHT=250 ALT=\"Advertisement\"></A>\r\n</NOSCRIPT>\r\n</IFRAME><span id=\"te-clearads-js-tradedesk01cont1\"><script type=\"text/javascript\" src=\"https://choices.truste.com/ca?pid=tradedesk01&aid=tradedesk01&cid=10312015&c=tradedesk01cont1&js=pmw0&w=300&h=250&sid=0\"></script></span>\r</span> <!-- PubMatic Ad Ends --><div style=\"position:absolute;left:0px;top:0px;visibility:hidden;\"><img src=\"http://aktrack.pubmatic.com/AdServer/AdDisplayTrackerServlet?operId=1&pubId=39741&siteId=66156&adId=148827&adServerId=243&kefact=5.939592&kaxefact=5.939592&kadNetFrequecy=1&kadwidth=300&kadheight=250&kadsizeid=9&kltstamp=1462919239&indirectAdId=0&adServerOptimizerId=2&ranreq=0.8652068939929505&kpbmtpfact=8.505987&dcId=1&tldId=19194842&passback=0&imprId=8025E377-EC45-4EB6-826C-49D56CCE47DF&oid=8025E377-EC45-4EB6-826C-49D56CCE47DF&ias=272&crID=p84y3ree&campaignId=6810&creativeId=0&pctr=0.000000&wDSPByrId=1362&pageURL=http%253A%252F%252Fprebid.org%253A9999%252Fgpt.html&lpu=www.etrade.com\"></div>", - 'dealId': '', - 'responseTimestamp': 1462919239544, - 'requestTimestamp': 1462919238922, - 'bidder': 'pubmatic', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'timeToRespond': 622, - 'pbLg': '5.00', - 'pbMg': '5.90', - 'pbHg': '5.93', - 'pbAg': '5.90', - 'size': '300x250', - 'requestId': 654321, - 'adserverTargeting': { - 'hb_bidder': 'pubmatic', - 'hb_adid': '28f4039c636b6a7', - 'hb_pb': '10.00', - 'hb_size': '300x250', - 'foobar': '300x250' - } - }, - { - 'bidderCode': 'rubicon', - 'width': 300, - 'height': 600, - 'statusMessage': 'Bid available', - 'adId': '29019e2ab586a5a', - 'cpm': 2.74, - 'ad': '<script type="text/javascript">;(function (rt, fe) { rt.renderCreative(fe, "/19968336/header-bid-tag-0", "10"); }((parent.window.rubicontag || window.top.rubicontag), (document.body || document.documentElement)));</script>', - 'responseTimestamp': 1462919239860, - 'requestTimestamp': 1462919238934, - 'bidder': 'rubicon', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'timeToRespond': 926, - 'pbLg': '2.50', - 'pbMg': '2.70', - 'pbHg': '2.74', - 'pbAg': '2.70', - 'size': '300x600', - 'requestId': 654321, - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '29019e2ab586a5a', - 'hb_pb': '10.00', - 'hb_size': '300x600', - 'foobar': '300x600' - } - } - ] - } - }, 'getBidResponses returns info for current bid request'); + afterEach(function() { + auctionManager.createAuction.restore(); + adaptermanager.callBids.restore(); + }); - assert.deepEqual($$PREBID_GLOBAL$$.getAdserverTargeting(), { - '/19968336/header-bid-tag-0': { - 'foobar': '300x250', - 'hb_size': '300x250', - 'hb_pb': '10.00', - 'hb_adid': '233bcbee889d46d', - 'hb_bidder': 'appnexus' + it('should not queue bid requests when a previous bid request is in process', () => { + var requestObj1 = { + bidsBackHandler: function bidsBackHandlerCallback() {}, + timeout: 2000, + adUnits: auction1.getAdUnits() + }; + + var requestObj2 = { + bidsBackHandler: function bidsBackHandlerCallback() {}, + timeout: 2000, + adUnits: auction2.getAdUnits() + }; + + assert.equal(auctionManager.getBidsReceived().length, 8, '_bidsReceived contains 8 bids'); + + $$PREBID_GLOBAL$$.requestBids(requestObj1); + $$PREBID_GLOBAL$$.requestBids(requestObj2); + + assert.ok(spyCallBids.calledTwice, 'When two requests for bids are made both should be' + + ' callBids immediately'); + + let result = targeting.getAllTargeting(); // $$PREBID_GLOBAL$$.getAdserverTargeting(); + let expected = { + '/19968336/header-bid-tag-0': { + 'foobar': '0x0,300x250,300x600', + 'hb_size': '300x250', + 'hb_pb': '10.00', + 'hb_adid': '233bcbee889d46d', + 'hb_bidder': 'appnexus' + }, + '/19968336/header-bid-tag1': { + 'hb_bidder': 'appnexus', + 'hb_adid': '24bd938435ec3fc', + 'hb_pb': '10.00', + 'hb_size': '728x90', + 'foobar': '728x90' + } } - }, 'targeting info returned for current placements'); - resetAuction(); - adaptermanager.callBids.restore(); + assert.deepEqual(result, expected, 'targeting info returned for current placements'); + }); }); }); @@ -1117,31 +1307,6 @@ describe('Unit: Prebid Module', function () { }); }); - describe('addCallback', () => { - it('should log error and return null id when error registering callback', () => { - var spyLogError = sinon.spy(utils, 'logError'); - var id = $$PREBID_GLOBAL$$.addCallback('event', 'fakeFunction'); - assert.equal(id, null, 'id returned was null'); - assert.ok(spyLogError.calledWith('error registering callback. Check method signature'), - 'expected error was logged'); - utils.logError.restore(); - }); - - it('should add callback to bidmanager', () => { - var spyAddCallback = sinon.spy(bidmanager, 'addCallback'); - var id = $$PREBID_GLOBAL$$.addCallback('event', Function); - assert.ok(spyAddCallback.calledWith(id, Function, 'event'), 'called bidmanager.addCallback'); - bidmanager.addCallback.restore(); - }); - }); - - describe('removeCallback', () => { - it('should return null', () => { - const id = $$PREBID_GLOBAL$$.removeCallback(); - assert.equal(id, null); - }); - }); - describe('registerBidAdapter', () => { it('should register bidAdaptor with adaptermanager', () => { var registerBidAdapterSpy = sinon.spy(adaptermanager, 'registerBidAdapter'); @@ -1163,19 +1328,6 @@ describe('Unit: Prebid Module', function () { }); }); - describe('bidsAvailableForAdapter', () => { - it('should update requested bid with status set to available', () => { - const bidderCode = 'appnexus'; - $$PREBID_GLOBAL$$.bidsAvailableForAdapter(bidderCode); - - const requestedBids = $$PREBID_GLOBAL$$._bidsRequested.find(bid => bid.bidderCode === bidderCode); - requestedBids.bids.forEach(bid => { - assert.equal(bid.bidderCode, bidderCode, 'bidderCode was set'); - assert.equal(bid.statusMessage, 'Bid available', 'bid set as available'); - }); - }); - }); - describe('createBid', () => { it('should return a bid object', () => { const statusCode = 1; @@ -1189,18 +1341,6 @@ describe('Unit: Prebid Module', function () { }); }); - describe('addBidResponse', () => { - it('should call bidmanager.addBidResponse', () => { - const addBidResponseStub = sinon.stub(bidmanager, 'addBidResponse'); - const adUnitCode = 'testcode'; - const bid = $$PREBID_GLOBAL$$.createBid(0); - - $$PREBID_GLOBAL$$.addBidResponse(adUnitCode, bid); - assert.ok(addBidResponseStub.calledWith(adUnitCode, bid), 'called bidmanager.addBidResponse'); - bidmanager.addBidResponse.restore(); - }); - }); - describe('loadScript', () => { it('should call adloader.loadScript', () => { const loadScriptSpy = sinon.spy(adloader, 'loadScript'); @@ -1214,79 +1354,6 @@ describe('Unit: Prebid Module', function () { }); }); - // describe('enableAnalytics', () => { - // let logErrorSpy; - // - // beforeEach(() => { - // logErrorSpy = sinon.spy(utils, 'logError'); - // }); - // - // afterEach(() => { - // utils.logError.restore(); - // }); - // - // it('should log error when not passed options', () => { - // const error = '$$PREBID_GLOBAL$$.enableAnalytics should be called with option {}'; - // $$PREBID_GLOBAL$$.enableAnalytics(); - // assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); - // }); - // - // it('should call ga.enableAnalytics with options', () => { - // const enableAnalyticsSpy = sinon.spy(ga, 'enableAnalytics'); - // - // let options = {'provider': 'ga'}; - // $$PREBID_GLOBAL$$.enableAnalytics(options); - // assert.ok(enableAnalyticsSpy.calledWith({}), 'ga.enableAnalytics called with empty options object'); - // - // options['options'] = 'testoptions'; - // $$PREBID_GLOBAL$$.enableAnalytics(options); - // assert.ok(enableAnalyticsSpy.calledWith(options.options), 'ga.enableAnalytics called with provided options'); - // - // ga.enableAnalytics.restore(); - // }); - // - // it('should catch errors thrown from ga.enableAnalytics', () => { - // const error = {message: 'Error calling GA: '}; - // const enableAnalyticsStub = sinon.stub(ga, 'enableAnalytics').throws(error); - // const options = {'provider': 'ga'}; - // - // $$PREBID_GLOBAL$$.enableAnalytics(options); - // assert.ok(logErrorSpy.calledWith(error.message), 'expected error was caught'); - // ga.enableAnalytics.restore(); - // }); - // - // it('should return null for other providers', () => { - // const options = {'provider': 'other_provider'}; - // const returnValue = $$PREBID_GLOBAL$$.enableAnalytics(options); - // assert.equal(returnValue, null, 'expected return value'); - // }); - // }); - - describe('sendTimeoutEvent', () => { - it('should emit BID_TIMEOUT for timed out bids', () => { - const eventsEmitSpy = sinon.spy(events, 'emit'); - - var requestObj = { - bidsBackHandler: function bidsBackHandlerCallback() {}, - timeout: 20 - }; - var adUnits = [{ - code: 'code', - bids: [{ - bidder: 'appnexus', - params: { placementId: '123' } - }] - }]; - $$PREBID_GLOBAL$$.adUnits = adUnits; - $$PREBID_GLOBAL$$.requestBids(requestObj); - - setTimeout(function () { - assert.ok(eventsEmitSpy.calledWith(CONSTANTS.EVENTS.BID_TIMEOUT), 'emitted events BID_TIMEOUT'); - events.emit.restore(); - }, 100); - }); - }); - describe('aliasBidder', () => { it('should call adaptermanager.aliasBidder', () => { const aliasBidAdapterSpy = sinon.spy(adaptermanager, 'aliasBidAdapter'); @@ -1360,101 +1427,16 @@ describe('Unit: Prebid Module', function () { }); }); - describe('getAllWinningBids', () => { - it('should return all winning bids', () => { - const bids = {name: 'a winning bid'}; - $$PREBID_GLOBAL$$._winningBids = bids; - - assert.deepEqual($$PREBID_GLOBAL$$.getAllWinningBids(), bids); - - $$PREBID_GLOBAL$$._winningBids = []; - }); - }); - describe('emit event', () => { - it('should call AUCTION_END only once', () => { - resetAuction(); - var spyClearAuction = sinon.spy($$PREBID_GLOBAL$$, 'clearAuction'); - var clock1 = sinon.useFakeTimers(); - - var requestObj = { - bidsBackHandler: function bidsBackHandlerCallback() {}, - timeout: 2000, - }; - - $$PREBID_GLOBAL$$.requestBids(requestObj); - clock1.tick(2001); - assert.ok(spyClearAuction.calledOnce, true); - - $$PREBID_GLOBAL$$._bidsRequested = [{ - 'bidderCode': 'appnexus', - 'requestId': '1863e370099523', - 'bidderRequestId': '2946b569352ef2', - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '4799418', - 'test': 'me' - }, - 'placementCode': '/19968336/header-bid-tag1', - 'sizes': [[728, 90], [970, 90]], - 'bidId': '392b5a6b05d648', - 'bidderRequestId': '2946b569352ef2', - 'requestId': '1863e370099523', - 'startTime': 1462918897462, - 'status': 1 - } - ], - 'start': 1462918897460 - }]; - - $$PREBID_GLOBAL$$._bidsReceived = []; - - var bid = Object.assign({ - 'bidderCode': 'appnexus', - 'width': 728, - 'height': 90, - 'statusMessage': 'Bid available', - 'adId': '24bd938435ec3fc', - 'creative_id': 33989846, - 'cpm': 0, - 'adUrl': 'http://lax1-ib.adnxs.com/ab?e=wqT_3QLyBKhyAgAAAwDWAAUBCMjAybkFEOOryfjI7rGNWhjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbJmhBYweAnYABokUB4mt0CgAEBigEDVVNEkgUG8ECYAdgFoAFaqAEBsAEAuAEBwAEDyAEA0AEA2AEA4AEA8AEAigI6dWYoJ2EnLCA0OTQ0NzIsIDE0NjI5MTkyNDApOwEcLHInLCAzMzk4OTg0NjYeAPBvkgLNASFwU2Y1YUFpNjBJY0VFTmJKbWhBWUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCd3lnNTRDb0FCcGh5SUFRcVFBUUdZQVFHZ0FRR29BUU93QVFDNUFRQUFBQUFBQU9BX3dRRQkMSEFEZ1A4a0JJNTJDbGs5VjB6X1oVKCRQQV80QUVBOVFFBSw8bUFLS2dNQ0NENkFDQUxVQwUVBEwwCQh0T0FDQU9nQ0FQZ0NBSUFEQVEuLpoCJSFfZ2lqYXdpMtAA8KZ3ZUFuSUFRb2lvREFnZzgu2ALoB-ACx9MB6gIfaHR0cDovL3ByZWJpZC5vcmc6OTk5OS9ncHQuaHRtbIADAIgDAZADAJgDBaADAaoDALADALgDAMADrALIAwDYAwDgAwDoAwD4AwOABACSBAQvanB0mAQAogQKMTAuMS4xMy4zN6gEi-wJsgQICAAQABgAIAC4BADABADIBADSBAsxMC4wLjgwLjI0MA..&s=1f584d32c2d7ae3ce3662cfac7ca24e710bc7fd0&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html', - 'responseTimestamp': 1462919239342, - 'requestTimestamp': 1462919238919, - 'bidder': 'appnexus', - 'adUnitCode': '/19968336/header-bid-tag1', - 'timeToRespond': 423, - 'pbLg': '5.00', - 'pbMg': '10.00', - 'pbHg': '10.00', - 'pbAg': '10.00', - 'size': '728x90', - 'alwaysUseBid': true, - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '24bd938435ec3fc', - 'hb_pb': '10.00', - 'hb_size': '728x90', - 'foobar': '728x90' - } - }, bidfactory.createBid(2)); - - var adUnits = [{ - code: '/19968336/header-bid-tag1', - bids: [{ - bidder: 'appnexus', - params: { placementId: '123' } - }] - }]; - $$PREBID_GLOBAL$$.adUnits = adUnits; - - const adUnitCode = '/19968336/header-bid-tag1'; - $$PREBID_GLOBAL$$.addBidResponse(adUnitCode, bid); - assert.equal(spyClearAuction.callCount, 1, 'AUCTION_END event emitted more than once'); + let auctionManagerStub; + beforeEach(() => { + auctionManagerStub = sinon.stub(auctionManager, 'createAuction').callsFake(function() { + return auction; + }); + }); - clock1.restore(); - resetAuction(); + afterEach(() => { + auctionManager.createAuction.restore(); }); }); @@ -1519,7 +1501,7 @@ describe('Unit: Prebid Module', function () { 'pbAg': '10.00', 'size': '300x250', 'alwaysUseBid': true, - 'requestId': 123456, + 'auctionId': 123456, 'adserverTargeting': { 'hb_bidder': 'appnexus', 'hb_adid': '233bcbee889d46d', @@ -1538,155 +1520,18 @@ describe('Unit: Prebid Module', function () { }); }); - describe('video adserverTag', () => { - var adserverTag = 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/19968336/header-bid-tag-0&impl=s&gdfp_req=1&env=vp&output=xml_vast2&unviewed_position_start=1&url=www.test.com'; - - var options = { - 'adserver': 'dfp', - 'code': '/19968336/header-bid-tag-0' - }; - - beforeEach(() => { - resetAuction(); - $$PREBID_GLOBAL$$._bidsReceived = [ - { - 'bidderCode': 'appnexusAstDummyName', - 'width': 0, - 'height': 0, - 'statusMessage': 'Bid returned empty or error response', - 'adId': '233bcbee889d46d', - 'requestId': 123456, - 'responseTimestamp': 1462919238959, - 'requestTimestamp': 1462919238910, - 'cpm': 0, - 'bidder': 'appnexus', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'timeToRespond': 49, - 'pbLg': '0.00', - 'pbMg': '0.00', - 'pbHg': '0.00', - 'pbAg': '0.00', - 'pbDg': '0.00', - 'pbCg': '', - 'adserverTargeting': {} - }, - { - 'bidderCode': 'appnexusAst', - 'dealId': '1234', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '233bcbee889d46d', - 'creative_id': 29681110, - 'cpm': 10, - 'vastUrl': 'http://www.simplevideoad.com/', - 'descriptionUrl': 'http://www.simplevideoad.com/', - 'responseTimestamp': 1462919239340, - 'requestTimestamp': 1462919238919, - 'bidder': 'appnexus', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'timeToRespond': 421, - 'pbLg': '5.00', - 'pbMg': '10.00', - 'pbHg': '10.00', - 'pbAg': '10.00', - 'size': '300x250', - 'alwaysUseBid': true, - 'requestId': 123456, - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '233bcbee889d46d', - 'hb_pb': '10.00', - 'hb_size': '300x250', - 'foobar': '300x250', - 'hb_deal_appnexusAst': '1234' - } - } - ]; - }); - - afterEach(() => { - resetAuction(); - }); - - it('should log error when adserver is not dfp', () => { - var logErrorSpy = sinon.spy(utils, 'logError'); - var options = { - 'adserver': 'anyother', - 'code': '/19968336/header-bid-tag-0' - }; - var masterTagUrl = $$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag(adserverTag, options); - assert.ok(logErrorSpy.calledOnce, true); - utils.logError.restore(); - }); - - it('should return original adservertag if bids empty', () => { - $$PREBID_GLOBAL$$._bidsReceived = []; - var masterTagUrl = $$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag(adserverTag, options); - expect(masterTagUrl).to.equal(adserverTag); - }); - - it('should return original adservertag if there are no bids for the given placement code', () => { - // urls.js:parse returns port 443 for IE11, blank for other browsers - const ie11port = !!window.MSInputMethodContext && !!document.documentMode ? ':443' : ''; - const adserverTag = `https://pubads.g.doubleclick.net${ie11port}/gampad/ads?sz=640x480&iu=/19968336/header-bid-tag-0&impl=s&gdfp_req=1&env=vp&output=xml_vast2&unviewed_position_start=1&url=www.test.com`; - - const masterTagUrl = $$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag(adserverTag, { - 'adserver': 'dfp', - 'code': 'one-without-bids' - }); - - expect(masterTagUrl).to.equal(adserverTag); - }); - - it('should log error when google\'s parameters are missing in adserverTag', () => { - var logErrorSpy = sinon.spy(utils, 'logError'); - var adserverTag = 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/19968336/header-bid-tag-0&impl=s&gdfp_req=1&env=vp&output=xml_vast2&unviewed_position_start=1&url=www.test.com'; - var masterTagUrl = $$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag(adserverTag, options); - assert.ok(logErrorSpy.calledOnce, true); - utils.logError.restore(); - }); - - it('should append parameters to the adserverTag', () => { - var masterTagUrl = $$PREBID_GLOBAL$$.buildMasterVideoTagFromAdserverTag(adserverTag, options); - var masterTagUrlParsed = urlParse(masterTagUrl, true); - var masterTagQuery = masterTagUrlParsed.query; - var expectedTargetingQuery = 'hb_bidder=appnexus&hb_adid=233bcbee889d46d&hb_pb=10.00&hb_size=300x250&foobar=300x250&hb_deal_appnexusAst=1234'; - - expect(masterTagQuery).to.have.property('cust_params').and.to.equal(expectedTargetingQuery); - expect(masterTagQuery).to.have.property('description_url').and.to.equal('http://www.simplevideoad.com/'); - }); - }); - - describe('bidderSequence', () => { - it('setting to `random` uses shuffled order of adUnits', () => { - sinon.spy(utils, 'shuffle'); - const requestObj = { - bidsBackHandler: function bidsBackHandlerCallback() {}, - timeout: 2000 - }; - - $$PREBID_GLOBAL$$.setConfig({ bidderSequence: 'random' }); - $$PREBID_GLOBAL$$.requestBids(requestObj); - - sinon.assert.calledOnce(utils.shuffle); - utils.shuffle.restore(); - resetAuction(); - }); - }); - describe('getHighestCpm', () => { - it('returns an array of winning bid objects for each adUnit', () => { - const highestCpmBids = $$PREBID_GLOBAL$$.getHighestCpmBids(); - expect(highestCpmBids.length).to.equal(2); - expect(highestCpmBids[0]).to.deep.equal($$PREBID_GLOBAL$$._bidsReceived[1]); - expect(highestCpmBids[1]).to.deep.equal($$PREBID_GLOBAL$$._bidsReceived[2]); - }); + // it('returns an array of winning bid objects for each adUnit', () => { + // const highestCpmBids = $$PREBID_GLOBAL$$.getHighestCpmBids(); + // expect(highestCpmBids.length).to.equal(2); + // expect(highestCpmBids[0]).to.deep.equal(auctionManager.getBidsReceived()[1]); + // expect(highestCpmBids[1]).to.deep.equal(auctionManager.getBidsReceived()[2]); + // }); it('returns an array containing the highest bid object for the given adUnitCode', () => { const highestCpmBids = $$PREBID_GLOBAL$$.getHighestCpmBids('/19968336/header-bid-tag-0'); expect(highestCpmBids.length).to.equal(1); - expect(highestCpmBids[0]).to.deep.equal($$PREBID_GLOBAL$$._bidsReceived[1]); + expect(highestCpmBids[0]).to.deep.equal(auctionManager.getBidsReceived()[1]); }); it('returns an empty array when the given adUnit is not found', () => { @@ -1695,8 +1540,10 @@ describe('Unit: Prebid Module', function () { }); it('returns an empty array when the given adUnit has no bids', () => { - $$PREBID_GLOBAL$$._bidsReceived = [$$PREBID_GLOBAL$$._bidsReceived[0]]; - $$PREBID_GLOBAL$$._bidsReceived[0].cpm = 0; + let _bidsReceived = getBidResponses()[0]; + _bidsReceived.cpm = 0; + auction.getBidsReceived = function() { return _bidsReceived }; + const highestCpmBids = $$PREBID_GLOBAL$$.getHighestCpmBids('/19968336/header-bid-tag-0'); expect(highestCpmBids.length).to.equal(0); resetAuction(); @@ -1704,34 +1551,45 @@ describe('Unit: Prebid Module', function () { }); describe('setTargetingForAst', () => { + let targeting; + let auctionManagerInstance; + beforeEach(() => { resetAuction(); + auctionManagerInstance = newAuctionManager(); + sinon.stub(auctionManagerInstance, 'getBidsReceived').callsFake(function() { + return [getBidResponses()[1]]; + }); + sinon.stub(auctionManagerInstance, 'getAdUnitCodes').callsFake(function() { + return ['/19968336/header-bid-tag-0']; + }); + targeting = newTargeting(auctionManagerInstance); }); afterEach(() => { + auctionManagerInstance.getBidsReceived.restore(); + auctionManagerInstance.getAdUnitCodes.restore(); resetAuction(); }); it('should set targeting for appnexus apntag object', () => { + const bids = auctionManagerInstance.getBidsReceived(); const adUnitCode = '/19968336/header-bid-tag-0'; - const bidder = 'appnexus'; - const bids = $$PREBID_GLOBAL$$._bidsReceived.filter(bid => (bid.adUnitCode === adUnitCode && bid.bidderCode === bidder)); var expectedAdserverTargeting = bids[0].adserverTargeting; var newAdserverTargeting = {}; for (var key in expectedAdserverTargeting) { - var nkey = (key === 'hb_adid') ? key.toUpperCase() : key; - newAdserverTargeting[nkey] = expectedAdserverTargeting[key]; + newAdserverTargeting[key.toUpperCase()] = expectedAdserverTargeting[key]; } - $$PREBID_GLOBAL$$.setTargetingForAst(); + targeting.setTargetingForAst(); expect(newAdserverTargeting).to.deep.equal(window.apntag.tags[adUnitCode].keywords); }); it('should not find hb_adid key in lowercase for all bidders', () => { const adUnitCode = '/19968336/header-bid-tag-0'; $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); - $$PREBID_GLOBAL$$.setTargetingForAst(); + targeting.setTargetingForAst(); const keywords = Object.keys(window.apntag.tags[adUnitCode].keywords).filter(keyword => (keyword.substring(0, 'hb_adid'.length) === 'hb_adid')); expect(keywords.length).to.equal(0); }); @@ -1771,41 +1629,28 @@ describe('Unit: Prebid Module', function () { }); }); - describe('setS2SConfig', () => { - let logErrorSpy; - + describe('getAllPrebidWinningBids', () => { + let auctionManagerStub; beforeEach(() => { - logErrorSpy = sinon.spy(utils, 'logError'); + auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived'); }); afterEach(() => { - utils.logError.restore(); - }); - - it('should log error when accountId is missing', () => { - const options = { - enabled: true, - bidders: ['appnexus'], - timeout: 1000, - adapter: 'prebidServer', - endpoint: 'https://prebid.adnxs.com/pbs/v1/auction' - }; - - $$PREBID_GLOBAL$$.setConfig({ s2sConfig: {options} }); - assert.ok(logErrorSpy.calledOnce, true); + auctionManagerStub.restore(); }); - it('should log error when bidders is missing', () => { - const options = { - accountId: '1', - enabled: true, - timeout: 1000, - adapter: 's2s', - endpoint: 'https://prebid.adnxs.com/pbs/v1/auction' - }; + it('should return prebid auction winning bids', () => { + 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'}), + ]; + auctionManagerStub.returns(bidsReceived) + let bids = $$PREBID_GLOBAL$$.getAllPrebidWinningBids(); - $$PREBID_GLOBAL$$.setConfig({ s2sConfig: {options} }); - assert.ok(logErrorSpy.calledOnce, true); + expect(bids.length).to.equal(1); + expect(bids[0].adId).to.equal('adid-1'); }); }); }); diff --git a/test/spec/url_spec.js b/test/spec/url_spec.js index 3ffb8ad5ca7..cfa1b0c80b4 100644 --- a/test/spec/url_spec.js +++ b/test/spec/url_spec.js @@ -78,4 +78,17 @@ describe('helpers.url', () => { })).to.equal('http://example.com'); }); }); + + describe('parse(url, {decodeSearchAsString: true})', () => { + let parsed; + + beforeEach(() => { + parsed = parse('http://example.com:3000/pathname/?search=test&foo=bar&bar=foo%26foo%3Dxxx#hash', {decodeSearchAsString: true}); + }); + + it('extracts the search query', () => { + expect(parsed).to.have.property('search'); + expect(parsed.search).to.equal('?search=test&foo=bar&bar=foo&foo=xxx'); + }); + }); }); diff --git a/test/spec/userSync_spec.js b/test/spec/userSync_spec.js index d6ae525f6d7..9a72629c71a 100644 --- a/test/spec/userSync_spec.js +++ b/test/spec/userSync_spec.js @@ -10,6 +10,7 @@ describe('user sync', () => { let timeoutStub; let shuffleStub; let getUniqueIdentifierStrStub; + let insertUserSyncIframeStub; let idPrefix = 'test-generated-id-'; let lastId = 0; let defaultUserSyncConfig = config.getConfig('userSync'); @@ -23,13 +24,21 @@ describe('user sync', () => { browserSupportsCookies: !disableBrowserCookies, }) } + let clock; + before(() => { + clock = sinon.useFakeTimers(); + }); + + after(() => { + clock.restore(); + }); beforeEach(() => { triggerPixelStub = sinon.stub(utils, 'triggerPixel'); logWarnStub = sinon.stub(utils, 'logWarn'); - shuffleStub = sinon.stub(utils, 'shuffle', (array) => array.reverse()); - getUniqueIdentifierStrStub = sinon.stub(utils, 'getUniqueIdentifierStr', () => idPrefix + (lastId += 1)); - timeoutStub = sinon.stub(window, 'setTimeout', (callbackFunc) => { callbackFunc(); }); + shuffleStub = sinon.stub(utils, 'shuffle').callsFake((array) => array.reverse()); + getUniqueIdentifierStrStub = sinon.stub(utils, 'getUniqueIdentifierStr').callsFake(() => idPrefix + (lastId += 1)); + insertUserSyncIframeStub = sinon.stub(utils, 'insertUserSyncIframe'); }); afterEach(() => { @@ -37,7 +46,7 @@ describe('user sync', () => { logWarnStub.restore(); shuffleStub.restore(); getUniqueIdentifierStrStub.restore(); - timeoutStub.restore(); + insertUserSyncIframeStub.restore(); }); it('should register and fire a pixel URL', () => { @@ -59,7 +68,8 @@ describe('user sync', () => { userSync.registerSync('image', 'testBidder', 'http://example.com'); // This implicitly tests cookie and browser support userSync.syncUsers(999); - expect(timeoutStub.getCall(0).args[1]).to.equal(999); + clock.tick(1000); + expect(triggerPixelStub.getCall(0)).to.not.be.null; }); it('should register and fires multiple pixel URLs', () => { @@ -85,9 +95,7 @@ describe('user sync', () => { const userSync = newTestUserSync({iframeEnabled: true}); userSync.registerSync('iframe', 'testBidder', 'http://example.com/iframe'); userSync.syncUsers(); - let iframe = window.document.getElementById(idPrefix + lastId); - expect(iframe).to.exist; - expect(iframe.src).to.equal('http://example.com/iframe'); + expect(insertUserSyncIframeStub.getCall(0).args[0]).to.equal('http://example.com/iframe'); }); it('should only trigger syncs once per page', () => { diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 4ffc6a9f15f..9218409c46c 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,7 +1,8 @@ -import { getSlotTargeting, getAdServerTargeting } from 'test/fixtures/fixtures'; +import { getAdServerTargeting } from 'test/fixtures/fixtures'; +import { expect } from 'chai'; var assert = require('assert'); -var utils = require('../../src/utils'); +var utils = require('src/utils'); describe('Utils', function () { var obj_string = 's', @@ -100,7 +101,7 @@ describe('Utils', function () { var obj = getAdServerTargeting(); var output = utils.transformAdServerTargetingObj(obj[Object.keys(obj)[0]]); - var expected = 'foobar=300x250&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&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'; assert.equal(output, expected); }); @@ -358,6 +359,33 @@ describe('Utils', function () { }); }); + describe('isPlainObject', function () { + it('should return false with input string', function () { + var output = utils.isPlainObject(obj_string); + assert.deepEqual(output, false); + }); + + it('should return false with input number', function () { + var output = utils.isPlainObject(obj_number); + assert.deepEqual(output, false); + }); + + it('should return true with input object', function () { + var output = utils.isPlainObject(obj_object); + assert.deepEqual(output, true); + }); + + it('should return false with input array', function () { + var output = utils.isPlainObject(obj_array); + assert.deepEqual(output, false); + }); + + it('should return false with input function', function () { + var output = utils.isPlainObject(obj_function); + assert.deepEqual(output, false); + }); + }); + describe('isEmpty', function () { it('should return true with empty object', function () { var output = utils.isEmpty(obj_object); @@ -525,90 +553,6 @@ describe('Utils', function () { }); }); - describe('cookie support', function () { - // store original cookie getter and setter so we can reset later - var origCookieSetter = document.__lookupSetter__('cookie'); - var origCookieGetter = document.__lookupGetter__('cookie'); - - // store original cookieEnabled getter and setter so we can reset later - var origCookieEnabledSetter = window.navigator.__lookupSetter__('cookieEnabled'); - var origCookieEnabledGetter = window.navigator.__lookupGetter__('cookieEnabled'); - - // Replace the document cookie set function with the output of a custom function for testing - let setCookie = (v) => v; - - beforeEach(() => { - // Redefine window.navigator.cookieEnabled such that you can set otherwise "read-only" values - Object.defineProperty(window.navigator, 'cookieEnabled', (function (_value) { - return { - get: function _get() { - return _value; - }, - set: function _set(v) { - _value = v; - }, - configurable: true - }; - })(window.navigator.cookieEnabled)); - - // Reset the setCookie cookie function before each test - setCookie = (v) => v; - // Redefine the document.cookie object such that you can purposefully have it output nothing as if it is disabled - Object.defineProperty(window.document, 'cookie', (function (_value) { - return { - get: function _get() { - return _value; - }, - set: function _set(v) { - _value = setCookie(v); - }, - configurable: true - }; - })(window.navigator.cookieEnabled)); - }); - - afterEach(() => { - // redefine window.navigator.cookieEnabled to original getter and setter - Object.defineProperty(window.navigator, 'cookieEnabled', { - get: origCookieEnabledGetter, - set: origCookieEnabledSetter, - configurable: true - }); - // redefine document.cookie to original getter and setter - Object.defineProperty(document, 'cookie', { - get: origCookieGetter, - set: origCookieSetter, - configurable: true - }); - }); - - it('should be detected', function() { - assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be enabled by default'); - }); - - it('should be not available', function() { - setCookie = () => ''; - window.navigator.cookieEnabled = false; - window.document.cookie = ''; - assert.equal(utils.cookiesAreEnabled(), false, 'Cookies should be disabled'); - }); - - it('should be available', function() { - window.navigator.cookieEnabled = false; - window.document.cookie = 'key=value'; - assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should already be set'); - window.navigator.cookieEnabled = false; - window.document.cookie = ''; - assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should settable'); - setCookie = () => ''; - window.navigator.cookieEnabled = true; - window.document.cookie = ''; - assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be on via on window.navigator'); - // Reset the setCookie - setCookie = (v) => v; - }); - }); - describe('delayExecution', function () { it('should execute the core function after the correct number of calls', function () { const callback = sinon.spy(); @@ -653,6 +597,20 @@ describe('Utils', function () { }); }); + describe('createContentToExecuteExtScriptInFriendlyFrame', function () { + it('should return empty string if url is not passed', function () { + var output = utils.createContentToExecuteExtScriptInFriendlyFrame(); + assert.equal(output, ''); + }); + + it('should have URL in returned value if url is passed', function () { + var url = 'https://abcd.com/service?a=1&b=2&c=3'; + var output = utils.createContentToExecuteExtScriptInFriendlyFrame(url); + var expected = `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><base target="_top" /><script>inDapIF=true;</script></head><body><!--PRE_SCRIPT_TAG_MACRO--><script src="${url}"></script><!--POST_SCRIPT_TAG_MACRO--></body></html>`; + assert.equal(output, expected); + }); + }); + describe('getDefinedParams', () => { it('builds an object consisting of defined params', () => { const adUnit = { @@ -671,4 +629,164 @@ describe('Utils', function () { }); }); }); + + describe('deepClone', () => { + it('deep copies objects', () => { + const adUnit = [{ + code: 'swan', + mediaTypes: {video: {context: 'outstream'}}, + renderer: { + render: bid => player.render(bid), + url: '/video/renderer.js' + }, + bids: [{ + bidder: 'dharmaInitiative', + params: { placementId: '481516', } + }], + }]; + + const adUnitCopy = utils.deepClone(adUnit); + expect(adUnitCopy[0].renderer.url).to.be.a('string'); + expect(adUnitCopy[0].renderer.render).to.be.a('function'); + }); + }); + + describe('getUserConfiguredParams', () => { + const adUnits = [{ + code: 'adUnit1', + bids: [{ + bidder: 'bidder1', + params: { + key1: 'value1' + } + }, { + bidder: 'bidder2' + }] + }]; + + it('should return params configured', () => { + const output = utils.getUserConfiguredParams(adUnits, 'adUnit1', 'bidder1'); + const expected = [{ + key1: 'value1' + }]; + assert.deepEqual(output, expected); + }); + + it('should return array containting empty object, if bidder present and no params are configured', () => { + const output = utils.getUserConfiguredParams(adUnits, 'adUnit1', 'bidder2'); + const expected = [{}]; + assert.deepEqual(output, expected); + }); + + it('should return empty array, if bidder is not present', () => { + const output = utils.getUserConfiguredParams(adUnits, 'adUnit1', 'bidder3'); + const expected = []; + assert.deepEqual(output, expected); + }); + + it('should return empty array, if adUnit is not present', () => { + const output = utils.getUserConfiguredParams(adUnits, 'adUnit2', 'bidder3'); + const expected = []; + assert.deepEqual(output, expected); + }); + }); + + describe('getTopWindowLocation', () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('returns window.location if not in iFrame', () => { + sandbox.stub(utils, 'getWindowLocation').returns({ + href: 'https://www.google.com/', + ancestorOrigins: {}, + origin: 'https://www.google.com', + protocol: 'https', + host: 'www.google.com', + hostname: 'www.google.com', + port: '', + pathname: '/', + search: '', + hash: '' + }); + let windowSelfAndTopObject = { self: 'is same as top' }; + sandbox.stub(utils, 'getWindowSelf').returns( + windowSelfAndTopObject + ); + sandbox.stub(utils, 'getWindowTop').returns( + windowSelfAndTopObject + ); + var topWindowLocation = utils.getTopWindowLocation(); + expect(topWindowLocation).to.be.a('object'); + expect(topWindowLocation.href).to.equal('https://www.google.com/'); + expect(topWindowLocation.protocol).to.equal('https'); + expect(topWindowLocation.hostname).to.equal('www.google.com'); + expect(topWindowLocation.port).to.equal(''); + expect(topWindowLocation.pathname).to.equal('/'); + expect(topWindowLocation.hash).to.equal(''); + expect(topWindowLocation.search).to.equal(''); + expect(topWindowLocation.host).to.equal('www.google.com'); + }); + + it('returns parsed dom string from ancestorOrigins if in iFrame & ancestorOrigins is populated', () => { + sandbox.stub(utils, 'getWindowSelf').returns( + { self: 'is not same as top' } + ); + sandbox.stub(utils, 'getWindowTop').returns( + { top: 'is not same as self' } + ); + sandbox.stub(utils, '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'); + expect(topWindowLocation.href).to.equal('https://www.google.com/a/umich.edu/acs'); + expect(topWindowLocation.protocol).to.equal('https'); + expect(topWindowLocation.hostname).to.equal('www.google.com'); + expect(topWindowLocation.hash).to.equal(''); + expect(topWindowLocation.search).to.equal(''); + // note IE11 returns the default secure port, so we look for this alternate value as well in these tests + expect(topWindowLocation.port).to.be.oneOf([0, 443]); + expect(topWindowLocation.host).to.be.oneOf(['www.google.com', 'www.google.com:443']); + }); + + it('returns parsed referrer string if in iFrame but no ancestorOrigins', () => { + sandbox.stub(utils, 'getWindowSelf').returns( + { self: 'is not same as top' } + ); + sandbox.stub(utils, 'getWindowTop').returns( + { top: 'is not same as self' } + ); + sandbox.stub(utils, 'getAncestorOrigins').returns(null); + sandbox.stub(utils, '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/'); + expect(topWindowLocation.protocol).to.equal('https'); + expect(topWindowLocation.hostname).to.equal('www.example.com'); + expect(topWindowLocation.pathname).to.equal('/'); + expect(topWindowLocation.hash).to.equal(''); + expect(topWindowLocation.search).to.equal(''); + // note IE11 returns the default secure port, so we look for this alternate value as well in these tests + expect(topWindowLocation.port).to.be.oneOf([0, 443]); + expect(topWindowLocation.host).to.be.oneOf(['www.example.com', 'www.example.com:443']); + }); + }); + + describe('convertCamelToUnderscore', () => { + it('returns converted string value using underscore syntax instead of camelCase', () => { + let var1 = 'placementIdTest'; + let test1 = utils.convertCamelToUnderscore(var1); + expect(test1).to.equal('placement_id_test'); + + let var2 = 'my_test_value'; + let test2 = utils.convertCamelToUnderscore(var2); + expect(test2).to.equal(var2); + }); + }); }); diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index e9af314218e..b853da708fc 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -1,6 +1,7 @@ import 'mocha'; import chai from 'chai'; import { getCacheUrl, store } from 'src/videoCache'; +import { config } from 'src/config'; const should = chai.should(); @@ -48,9 +49,17 @@ describe('The video cache', () => { xhr = sinon.useFakeXMLHttpRequest(); requests = []; xhr.onCreate = (request) => requests.push(request); + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } + }) }); - afterEach(() => xhr.restore()); + afterEach(() => { + xhr.restore(); + config.resetConfig(); + }); it('should execute the callback with a successful result when store() is called', () => { const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; @@ -92,6 +101,20 @@ describe('The video cache', () => { assertRequestMade({ vastUrl: 'my-mock-url.com' }, expectedValue) }); + it('should make the expected request when store() is called on an ad with a vastUrl and a vastImpUrl', () => { + const expectedValue = `<VAST version="3.0"> + <Ad> + <Wrapper> + <AdSystem>prebid.org wrapper</AdSystem> + <VASTAdTagURI><![CDATA[my-mock-url.com]]></VASTAdTagURI> + <Impression><![CDATA[imptracker.com]]></Impression> + <Creatives></Creatives> + </Wrapper> + </Ad> + </VAST>`; + assertRequestMade({ vastUrl: 'my-mock-url.com', vastImpUrl: 'imptracker.com' }, expectedValue) + }); + it('should make the expected request when store() is called on an ad with vastXml', () => { const vastXml = '<VAST version="3.0"></VAST>'; assertRequestMade({ vastXml: vastXml }, vastXml); @@ -128,6 +151,18 @@ describe('The video cache', () => { }); describe('The getCache function', () => { + beforeEach(() => { + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } + }) + }); + + afterEach(() => { + config.resetConfig(); + }); + it('should return the expected URL', () => { const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; const url = getCacheUrl(uuid); diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 57a7f7a127e..06cd653f444 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,67 +1,89 @@ import { isValidVideoBid } from 'src/video'; -const utils = require('src/utils'); describe('video.js', () => { - afterEach(() => { - utils.getBidRequest.restore(); - }); - it('validates valid instream bids', () => { - sinon.stub(utils, 'getBidRequest', () => ({ - bidder: 'appnexusAst', - mediaTypes: { - video: { context: 'instream' }, - }, - })); - - const valid = isValidVideoBid({ + const bid = { + adId: '123abc', vastUrl: 'http://www.example.com/vastUrl' - }); - - expect(valid).to.be(true); + }; + const bidRequests = [{ + bids: [{ + bidId: '123abc', + bidder: 'appnexus', + mediaTypes: { + video: { context: 'instream' } + } + }] + }]; + const valid = isValidVideoBid(bid, bidRequests); + expect(valid).to.equal(true); }); it('catches invalid instream bids', () => { - sinon.stub(utils, 'getBidRequest', () => ({ - bidder: 'appnexusAst', - mediaTypes: { - video: { context: 'instream' }, - }, - })); + const bid = { + adId: '123abc' + }; + const bidRequests = [{ + bids: [{ + bidId: '123abc', + bidder: 'appnexus', + mediaTypes: { + video: { context: 'instream' } + } + }] + }]; + const valid = isValidVideoBid(bid, bidRequests); + expect(valid).to.equal(false); + }); - const valid = isValidVideoBid({}); + it('catches invalid bids when prebid-cache is disabled', () => { + const bidRequests = [{ + bids: [{ + bidder: 'vastOnlyVideoBidder', + mediaTypes: { video: {} }, + }] + }]; - expect(valid).to.be(false); + const valid = isValidVideoBid({ vastXml: '<xml>vast</xml>' }, bidRequests); + + expect(valid).to.equal(false); }); it('validates valid outstream bids', () => { - sinon.stub(utils, 'getBidRequest', () => ({ - bidder: 'appnexusAst', - mediaTypes: { - video: { context: 'outstream' }, - }, - })); - - const valid = isValidVideoBid({ + const bid = { + adId: '123abc', renderer: { url: 'render.url', render: () => true, } - }); - - expect(valid).to.be(true); + }; + const bidRequests = [{ + bids: [{ + bidId: '123abc', + bidder: 'appnexus', + mediaTypes: { + video: { context: 'outstream' } + } + }] + }]; + const valid = isValidVideoBid(bid, bidRequests); + expect(valid).to.equal(true); }); it('catches invalid outstream bids', () => { - sinon.stub(utils, 'getBidRequest', () => ({ - bidder: 'appnexusAst', - mediaTypes: { - video: { context: 'outstream' }, - }, - })); - - const valid = isValidVideoBid({}); - - expect(valid).to.be(false); + const bid = { + adId: '123abc' + }; + const bidRequests = [{ + bids: [{ + bidId: '123abc', + bidder: 'appnexus', + mediaTypes: { + video: { context: 'outstream' } + } + }] + }]; + const valid = isValidVideoBid(bid, bidRequests); + expect(valid).to.equal(false); }); }); diff --git a/test/test_index.js b/test/test_index.js new file mode 100644 index 00000000000..51323d87437 --- /dev/null +++ b/test/test_index.js @@ -0,0 +1,4 @@ +require('test/helpers/prebidGlobal.js'); + +var testsContext = require.context('.', true, /_spec$/); +testsContext.keys().forEach(testsContext); diff --git a/webpack.conf.js b/webpack.conf.js index 68db3a389f2..4b53aabef22 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -19,7 +19,7 @@ module.exports = { ], }, output: { - jsonpFunction: 'pbjsChunk' + jsonpFunction: prebid.globalVarName+"Chunk" }, module: { rules: [ @@ -29,9 +29,6 @@ module.exports = { use: [ { loader: 'babel-loader', - options: { - presets: ['es2015'] - } } ] }, @@ -41,9 +38,6 @@ module.exports = { use: [ { loader: 'babel-loader', - options: { - presets: ['es2015'] - } } ], }, diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index f6efe31c6a5..00000000000 --- a/yarn.lock +++ /dev/null @@ -1,9016 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@gulp-sourcemaps/identity-map@1.X": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/identity-map/-/identity-map-1.0.1.tgz#cfa23bc5840f9104ce32a65e74db7e7a974bbee1" - dependencies: - acorn "^5.0.3" - css "^2.2.1" - normalize-path "^2.1.1" - source-map "^0.5.6" - through2 "^2.0.3" - -"@gulp-sourcemaps/map-sources@1.X": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz#890ae7c5d8c877f6d384860215ace9d7ec945bda" - dependencies: - normalize-path "^2.0.1" - through2 "^2.0.3" - -JSONStream@^1.0.3: - version "1.3.1" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a" - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - -abbrev@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" - -abbrev@1.0.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" - -accepts@1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" - dependencies: - mime-types "~2.1.11" - negotiator "0.6.1" - -accepts@~1.2.12, accepts@~1.2.13: - version "1.2.13" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.2.13.tgz#e5f1f3928c6d95fd96558c36ec3d9d0de4a6ecea" - dependencies: - mime-types "~2.1.6" - negotiator "0.5.3" - -accepts@~1.3.0: - version "1.3.4" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" - dependencies: - mime-types "~2.1.16" - negotiator "0.6.1" - -acorn-dynamic-import@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" - dependencies: - acorn "^4.0.3" - -acorn-jsx@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" - dependencies: - acorn "^3.0.4" - -acorn@4.X, acorn@^4.0.3: - version "4.0.13" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" - -acorn@^3.0.0, acorn@^3.0.4, acorn@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" - -acorn@^5.0.0, acorn@^5.0.3, acorn@^5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.2.tgz#911cb53e036807cf0fa778dc5d370fbd864246d7" - -adm-zip@~0.4.3: - version "0.4.7" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.7.tgz#8606c2cbf1c426ce8c8ec00174447fd49b6eafc1" - -after@0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" - -agent-base@2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.1.1.tgz#d6de10d5af6132d5bd692427d46fc538539094c7" - dependencies: - extend "~3.0.0" - semver "~5.0.1" - -ajv-keywords@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" - -ajv-keywords@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" - -ajv@^4.7.0, ajv@^4.9.1: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - -ajv@^5.0.0, ajv@^5.1.5, ajv@^5.2.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39" - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - json-schema-traverse "^0.3.0" - json-stable-stringify "^1.0.1" - -align-text@^0.1.1, align-text@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" - -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - -ansi-escapes@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" - -ansi-html@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - -ansi-regex@^0.2.0, ansi-regex@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" - -ansi-regex@^1.0.0, ansi-regex@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-1.1.1.tgz#41c847194646375e6a1a5d10c3ca054ef9fc980d" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - -ansi-styles@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" - -ansi-styles@^2.0.1, ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - -ansi-styles@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" - dependencies: - color-convert "^1.9.0" - -anymatch@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" - dependencies: - micromatch "^2.1.5" - normalize-path "^2.0.0" - -append-transform@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" - dependencies: - default-require-extensions "^1.0.0" - -aproba@^1.0.3: - version "1.1.2" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1" - -archiver-utils@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-1.3.0.tgz#e50b4c09c70bf3d680e32ff1b7994e9f9d895174" - dependencies: - glob "^7.0.0" - graceful-fs "^4.1.0" - lazystream "^1.0.0" - lodash "^4.8.0" - normalize-path "^2.0.0" - readable-stream "^2.0.0" - -archiver@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-1.3.0.tgz#4f2194d6d8f99df3f531e6881f14f15d55faaf22" - dependencies: - archiver-utils "^1.3.0" - async "^2.0.0" - buffer-crc32 "^0.2.1" - glob "^7.0.0" - lodash "^4.8.0" - readable-stream "^2.0.0" - tar-stream "^1.5.0" - walkdir "^0.0.11" - zip-stream "^1.1.0" - -archiver@~0.14.3: - version "0.14.4" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-0.14.4.tgz#5b9ddb9f5ee1ceef21cb8f3b020e6240ecb4315c" - dependencies: - async "~0.9.0" - buffer-crc32 "~0.2.1" - glob "~4.3.0" - lazystream "~0.1.0" - lodash "~3.2.0" - readable-stream "~1.0.26" - tar-stream "~1.1.0" - zip-stream "~0.5.0" - -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - -are-we-there-yet@~1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" - dependencies: - sprintf-js "~1.0.2" - -arr-diff@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" - dependencies: - arr-flatten "^1.0.1" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - -arr-flatten@^1.0.1, arr-flatten@^1.0.3: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - -array-differ@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" - -array-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" - -array-find-index@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - -array-iterate@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-iterate/-/array-iterate-1.1.1.tgz#865bf7f8af39d6b0982c60902914ac76bc0108f6" - -array-slice@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" - -array-slice@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.0.0.tgz#e73034f00dcc1f40876008fd20feae77bd4b7c2f" - -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - dependencies: - array-uniq "^1.0.1" - -array-uniq@^1.0.1, array-uniq@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - -array.from@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/array.from/-/array.from-0.2.0.tgz#2c627b1b76dff2def2365fa052b65c3d585e5f6b" - -arraybuffer.slice@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" - -arrify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - -asn1.js@^4.0.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.1.tgz#48ba240b45a9280e94748990ba597d216617fd40" - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -asn1@0.1.11: - version "0.1.11" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.1.11.tgz#559be18376d08a4ec4dbe80877d27818639b2df7" - -asn1@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - -assert-plus@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160" - -assert-plus@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" - -assert@^1.1.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - dependencies: - util "0.10.3" - -assertion-error@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.0.tgz#c7f85438fdd466bc7ca16ab90c81513797a5d23b" - -assertion-error@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" - -ast-types@0.x.x: - version "0.9.12" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.12.tgz#b136300d67026625ae15326982ca9918e5db73c9" - -async-each@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" - -async@1.x, async@^1.3.0, async@^1.4.0, async@^1.5.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - -async@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/async/-/async-2.0.1.tgz#b709cc0280a9c36f09f4536be823c838a9049e25" - dependencies: - lodash "^4.8.0" - -async@^0.9.0, async@~0.9.0: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - -async@^2.0.0, async@^2.1.2, async@^2.1.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" - dependencies: - lodash "^4.14.0" - -async@~0.2.10, async@~0.2.6: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - -atob@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" - -atob@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" - -aws-sign2@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.5.0.tgz#c57103f7a17fc037f02d7c2e64b602ea223f7d63" - -aws-sign2@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" - -aws4@^1.2.1: - version "1.6.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" - -babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-core@6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.22.0.tgz#643deaeb520bcd2b06c11e39945c877e0200d128" - dependencies: - babel-code-frame "^6.22.0" - babel-generator "^6.22.0" - babel-helpers "^6.22.0" - babel-messages "^6.22.0" - babel-register "^6.22.0" - babel-runtime "^6.22.0" - babel-template "^6.22.0" - babel-traverse "^6.22.0" - babel-types "^6.22.0" - babylon "^6.11.0" - convert-source-map "^1.1.0" - debug "^2.1.1" - json5 "^0.5.0" - lodash "^4.2.0" - minimatch "^3.0.2" - path-is-absolute "^1.0.0" - private "^0.1.6" - slash "^1.0.0" - source-map "^0.5.0" - -babel-core@^6.0.0, babel-core@^6.0.14, babel-core@^6.0.2, babel-core@^6.17.0, babel-core@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" - dependencies: - babel-code-frame "^6.26.0" - babel-generator "^6.26.0" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.26.0" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - convert-source-map "^1.5.0" - debug "^2.6.8" - json5 "^0.5.1" - lodash "^4.17.4" - minimatch "^3.0.4" - path-is-absolute "^1.0.1" - private "^0.1.7" - slash "^1.0.0" - source-map "^0.5.6" - -babel-generator@^6.18.0, babel-generator@^6.22.0, babel-generator@^6.25.0, babel-generator@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" - dependencies: - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - detect-indent "^4.0.0" - jsesc "^1.3.0" - lodash "^4.17.4" - source-map "^0.5.6" - trim-right "^1.0.1" - -babel-helper-bindify-decorators@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz#14c19e5f142d7b47f19a52431e52b1ccbc40a330" - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" - dependencies: - babel-helper-explode-assignable-expression "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-builder-react-jsx@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" - dependencies: - babel-runtime "^6.26.0" - babel-types "^6.26.0" - esutils "^2.0.2" - -babel-helper-call-delegate@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-define-map@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-explode-assignable-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-explode-class@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz#7dc2a3910dee007056e1e31d640ced3d54eaa9eb" - dependencies: - babel-helper-bindify-decorators "^6.24.1" - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-function-name@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" - dependencies: - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-get-function-arity@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-hoist-variables@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-optimise-call-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-regex@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" - dependencies: - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-remap-async-to-generator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-replace-supers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" - dependencies: - babel-helper-optimise-call-expression "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helpers@^6.22.0, babel-helpers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-loader@^7.1.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.2.tgz#f6cbe122710f1aa2af4d881c6d5b54358ca24126" - dependencies: - find-cache-dir "^1.0.0" - loader-utils "^1.0.2" - mkdirp "^0.5.1" - -babel-messages@^6.22.0, babel-messages@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-check-es2015-constants@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-syntax-async-functions@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" - -babel-plugin-syntax-async-generators@^6.5.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a" - -babel-plugin-syntax-class-constructor-call@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz#9cb9d39fe43c8600bec8146456ddcbd4e1a76416" - -babel-plugin-syntax-class-properties@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" - -babel-plugin-syntax-decorators@^6.1.18, babel-plugin-syntax-decorators@^6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b" - -babel-plugin-syntax-do-expressions@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz#5747756139aa26d390d09410b03744ba07e4796d" - -babel-plugin-syntax-dynamic-import@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" - -babel-plugin-syntax-exponentiation-operator@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" - -babel-plugin-syntax-export-extensions@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721" - -babel-plugin-syntax-flow@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" - -babel-plugin-syntax-function-bind@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46" - -babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" - -babel-plugin-syntax-object-rest-spread@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" - -babel-plugin-syntax-trailing-function-commas@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" - -babel-plugin-system-import-transformer@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-system-import-transformer/-/babel-plugin-system-import-transformer-3.1.0.tgz#d37f0cae8e61ef39060208331d931b5e630d7c5f" - dependencies: - babel-plugin-syntax-dynamic-import "^6.18.0" - -babel-plugin-transform-async-generator-functions@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db" - dependencies: - babel-helper-remap-async-to-generator "^6.24.1" - babel-plugin-syntax-async-generators "^6.5.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-async-to-generator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" - dependencies: - babel-helper-remap-async-to-generator "^6.24.1" - babel-plugin-syntax-async-functions "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-class-constructor-call@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz#80dc285505ac067dcb8d6c65e2f6f11ab7765ef9" - dependencies: - babel-plugin-syntax-class-constructor-call "^6.18.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-class-properties@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" - dependencies: - babel-helper-function-name "^6.24.1" - babel-plugin-syntax-class-properties "^6.8.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-decorators-legacy@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.4.tgz#741b58f6c5bce9e6027e0882d9c994f04f366925" - dependencies: - babel-plugin-syntax-decorators "^6.1.18" - babel-runtime "^6.2.0" - babel-template "^6.3.0" - -babel-plugin-transform-decorators@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d" - dependencies: - babel-helper-explode-class "^6.24.1" - babel-plugin-syntax-decorators "^6.13.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-do-expressions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz#28ccaf92812d949c2cd1281f690c8fdc468ae9bb" - dependencies: - babel-plugin-syntax-do-expressions "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-arrow-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoping@^6.22.0, babel-plugin-transform-es2015-block-scoping@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" - dependencies: - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-plugin-transform-es2015-classes@^6.22.0, babel-plugin-transform-es2015-classes@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" - dependencies: - babel-helper-define-map "^6.24.1" - babel-helper-function-name "^6.24.1" - babel-helper-optimise-call-expression "^6.24.1" - babel-helper-replace-supers "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-destructuring@^6.22.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2015-duplicate-keys@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-for-of@^6.22.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-function-name@^6.22.0, babel-plugin-transform-es2015-function-name@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" - dependencies: - babel-plugin-transform-es2015-modules-commonjs "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-commonjs@^6.22.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" - dependencies: - babel-plugin-transform-strict-mode "^6.24.1" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-types "^6.26.0" - -babel-plugin-transform-es2015-modules-systemjs@^6.22.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-umd@^6.22.0, babel-plugin-transform-es2015-modules-umd@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" - dependencies: - babel-plugin-transform-es2015-modules-amd "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" - dependencies: - babel-helper-replace-supers "^6.24.1" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-parameters@^6.22.0, babel-plugin-transform-es2015-parameters@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" - dependencies: - babel-helper-call-delegate "^6.24.1" - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-spread@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-sticky-regex@^6.22.0, babel-plugin-transform-es2015-sticky-regex@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-template-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-typeof-symbol@^6.22.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es2015-unicode-regex@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - regexpu-core "^2.0.0" - -babel-plugin-transform-es3-member-expression-literals@6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es3-member-expression-literals/-/babel-plugin-transform-es3-member-expression-literals-6.22.0.tgz#733d3444f3ecc41bef8ed1a6a4e09657b8969ebb" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es3-property-literals@6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es3-property-literals/-/babel-plugin-transform-es3-property-literals-6.22.0.tgz#b2078d5842e22abf40f73e8cde9cd3711abd5758" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-exponentiation-operator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" - dependencies: - babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" - babel-plugin-syntax-exponentiation-operator "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-export-extensions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz#53738b47e75e8218589eea946cbbd39109bbe653" - dependencies: - babel-plugin-syntax-export-extensions "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-flow-strip-types@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" - dependencies: - babel-plugin-syntax-flow "^6.18.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-function-bind@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz#c6fb8e96ac296a310b8cf8ea401462407ddf6a97" - dependencies: - babel-plugin-syntax-function-bind "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-object-assign@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.22.0.tgz#f99d2f66f1a0b0d498e346c5359684740caa20ba" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-object-rest-spread@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" - dependencies: - babel-plugin-syntax-object-rest-spread "^6.8.0" - babel-runtime "^6.26.0" - -babel-plugin-transform-react-display-name@^6.23.0: - version "6.25.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-react-jsx-self@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e" - dependencies: - babel-plugin-syntax-jsx "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-react-jsx-source@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6" - dependencies: - babel-plugin-syntax-jsx "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-react-jsx@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" - dependencies: - babel-helper-builder-react-jsx "^6.24.1" - babel-plugin-syntax-jsx "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-regenerator@^6.22.0, babel-plugin-transform-regenerator@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" - dependencies: - regenerator-transform "^0.10.0" - -babel-plugin-transform-strict-mode@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-preset-es2015@6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.22.0.tgz#af5a98ecb35eb8af764ad8a5a05eb36dc4386835" - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.22.0" - babel-plugin-transform-es2015-classes "^6.22.0" - babel-plugin-transform-es2015-computed-properties "^6.22.0" - babel-plugin-transform-es2015-destructuring "^6.22.0" - babel-plugin-transform-es2015-duplicate-keys "^6.22.0" - babel-plugin-transform-es2015-for-of "^6.22.0" - babel-plugin-transform-es2015-function-name "^6.22.0" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.22.0" - babel-plugin-transform-es2015-modules-commonjs "^6.22.0" - babel-plugin-transform-es2015-modules-systemjs "^6.22.0" - babel-plugin-transform-es2015-modules-umd "^6.22.0" - babel-plugin-transform-es2015-object-super "^6.22.0" - babel-plugin-transform-es2015-parameters "^6.22.0" - babel-plugin-transform-es2015-shorthand-properties "^6.22.0" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.22.0" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.22.0" - babel-plugin-transform-es2015-unicode-regex "^6.22.0" - babel-plugin-transform-regenerator "^6.22.0" - -babel-preset-es2015@^6.16.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939" - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.24.1" - babel-plugin-transform-es2015-classes "^6.24.1" - babel-plugin-transform-es2015-computed-properties "^6.24.1" - babel-plugin-transform-es2015-destructuring "^6.22.0" - babel-plugin-transform-es2015-duplicate-keys "^6.24.1" - babel-plugin-transform-es2015-for-of "^6.22.0" - babel-plugin-transform-es2015-function-name "^6.24.1" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.24.1" - babel-plugin-transform-es2015-modules-commonjs "^6.24.1" - babel-plugin-transform-es2015-modules-systemjs "^6.24.1" - babel-plugin-transform-es2015-modules-umd "^6.24.1" - babel-plugin-transform-es2015-object-super "^6.24.1" - babel-plugin-transform-es2015-parameters "^6.24.1" - babel-plugin-transform-es2015-shorthand-properties "^6.24.1" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.24.1" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.22.0" - babel-plugin-transform-es2015-unicode-regex "^6.24.1" - babel-plugin-transform-regenerator "^6.24.1" - -babel-preset-flow@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d" - dependencies: - babel-plugin-transform-flow-strip-types "^6.22.0" - -babel-preset-react@^6.16.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380" - dependencies: - babel-plugin-syntax-jsx "^6.3.13" - babel-plugin-transform-react-display-name "^6.23.0" - babel-plugin-transform-react-jsx "^6.24.1" - babel-plugin-transform-react-jsx-self "^6.22.0" - babel-plugin-transform-react-jsx-source "^6.22.0" - babel-preset-flow "^6.23.0" - -babel-preset-stage-0@^6.16.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz#5642d15042f91384d7e5af8bc88b1db95b039e6a" - dependencies: - babel-plugin-transform-do-expressions "^6.22.0" - babel-plugin-transform-function-bind "^6.22.0" - babel-preset-stage-1 "^6.24.1" - -babel-preset-stage-1@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz#7692cd7dcd6849907e6ae4a0a85589cfb9e2bfb0" - dependencies: - babel-plugin-transform-class-constructor-call "^6.24.1" - babel-plugin-transform-export-extensions "^6.22.0" - babel-preset-stage-2 "^6.24.1" - -babel-preset-stage-2@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1" - dependencies: - babel-plugin-syntax-dynamic-import "^6.18.0" - babel-plugin-transform-class-properties "^6.24.1" - babel-plugin-transform-decorators "^6.24.1" - babel-preset-stage-3 "^6.24.1" - -babel-preset-stage-3@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395" - dependencies: - babel-plugin-syntax-trailing-function-commas "^6.22.0" - babel-plugin-transform-async-generator-functions "^6.24.1" - babel-plugin-transform-async-to-generator "^6.24.1" - babel-plugin-transform-exponentiation-operator "^6.24.1" - babel-plugin-transform-object-rest-spread "^6.22.0" - -babel-register@^6.22.0, babel-register@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" - dependencies: - babel-core "^6.26.0" - babel-runtime "^6.26.0" - core-js "^2.5.0" - home-or-tmp "^2.0.0" - lodash "^4.17.4" - mkdirp "^0.5.1" - source-map-support "^0.4.15" - -babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -babel-template@^6.16.0, babel-template@^6.22.0, babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" - dependencies: - babel-runtime "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - lodash "^4.17.4" - -babel-traverse@^6.16.0, babel-traverse@^6.18.0, babel-traverse@^6.22.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" - dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" - lodash "^4.17.4" - -babel-types@^6.16.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.24.1, babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - -babelify@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/babelify/-/babelify-7.3.0.tgz#aa56aede7067fd7bd549666ee16dc285087e88e5" - dependencies: - babel-core "^6.0.14" - object-assign "^4.0.0" - -babylon@^6.11.0, babylon@^6.17.2, babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - -backo2@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" - -bail@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.2.tgz#f7d6c1731630a9f9f0d4d35ed1f962e2074a1764" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - -base64-arraybuffer@0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" - -base64-js@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" - -base64-url@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/base64-url/-/base64-url-1.2.1.tgz#199fd661702a0e7b7dcae6e0698bb089c52f6d78" - -base64id@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" - -base@^0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.1.tgz#b36a7f11113853a342a15691d98e2dcc8a6cc270" - dependencies: - arr-union "^3.1.0" - cache-base "^0.8.4" - class-utils "^0.3.4" - component-emitter "^1.2.1" - define-property "^0.2.5" - isobject "^2.1.0" - lazy-cache "^2.0.1" - mixin-deep "^1.1.3" - pascalcase "^0.1.1" - -basic-auth-connect@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz#fdb0b43962ca7b40456a7c2bb48fe173da2d2122" - -basic-auth@~1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.0.4.tgz#030935b01de7c9b94a824b29f3fccb750d3a5290" - -batch@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.5.3.tgz#3f3414f380321743bfc1042f9a83ff1d5824d464" - -bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" - dependencies: - tweetnacl "^0.14.3" - -beeper@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" - -better-assert@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" - dependencies: - callsite "1.0.0" - -big.js@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978" - -binary-extensions@^1.0.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" - -"binary@>= 0.3.0 < 1": - version "0.3.0" - resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" - dependencies: - buffers "~0.1.1" - chainsaw "~0.1.0" - -binaryextensions@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-1.0.1.tgz#1e637488b35b58bda5f4774bf96a5212a8c90755" - -bl@^0.9.0, bl@~0.9.0: - version "0.9.5" - resolved "https://registry.yarnpkg.com/bl/-/bl-0.9.5.tgz#c06b797af085ea00bc527afc8efcf11de2232054" - dependencies: - readable-stream "~1.0.26" - -bl@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e" - dependencies: - readable-stream "^2.0.5" - -blob@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" - -block-loader@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/block-loader/-/block-loader-2.1.0.tgz#bbb398ad5a843c6c71f79a296f4b6df4b0257312" - -block-stream@*: - version "0.0.9" - resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - dependencies: - inherits "~2.0.0" - -bluebird@^3.3.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" - -body-parser@^1.16.1: - version "1.17.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee" - dependencies: - bytes "2.4.0" - content-type "~1.0.2" - debug "2.6.7" - depd "~1.1.0" - http-errors "~1.6.1" - iconv-lite "0.4.15" - on-finished "~2.3.0" - qs "6.4.0" - raw-body "~2.2.0" - type-is "~1.6.15" - -body-parser@~1.13.3: - version "1.13.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.13.3.tgz#c08cf330c3358e151016a05746f13f029c97fa97" - dependencies: - bytes "2.1.0" - content-type "~1.0.1" - debug "~2.2.0" - depd "~1.0.1" - http-errors "~1.3.1" - iconv-lite "0.4.11" - on-finished "~2.3.0" - qs "4.0.0" - raw-body "~2.1.2" - type-is "~1.6.6" - -body-parser@~1.14.0: - version "1.14.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.14.2.tgz#1015cb1fe2c443858259581db53332f8d0cf50f9" - dependencies: - bytes "2.2.0" - content-type "~1.0.1" - debug "~2.2.0" - depd "~1.1.0" - http-errors "~1.3.1" - iconv-lite "0.4.13" - on-finished "~2.3.0" - qs "5.2.0" - raw-body "~2.1.5" - type-is "~1.6.10" - -body@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" - dependencies: - continuable-cache "^0.3.1" - error "^7.0.0" - raw-body "~1.1.0" - safe-json-parse "~1.0.1" - -boom@0.4.x: - version "0.4.2" - resolved "https://registry.yarnpkg.com/boom/-/boom-0.4.2.tgz#7a636e9ded4efcefb19cef4947a3c67dfaee911b" - dependencies: - hoek "0.9.x" - -boom@2.x.x: - version "2.10.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" - dependencies: - hoek "2.x.x" - -brace-expansion@^1.0.0, brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^0.1.2: - version "0.1.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-0.1.5.tgz#c085711085291d8b75fdd74eab0f8597280711e6" - dependencies: - expand-range "^0.1.0" - -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -braces@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.2.2.tgz#241f868c2b2690d9febeee5a7c83fbbf25d00b1b" - dependencies: - arr-flatten "^1.0.3" - array-unique "^0.3.2" - define-property "^1.0.0" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.0" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^2.1.0" - to-regex "^3.0.1" - -brorand@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - -browser-resolve@^1.7.0: - version "1.11.2" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" - dependencies: - resolve "1.1.7" - -browser-stdout@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" - -browserify-aes@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-0.4.0.tgz#067149b668df31c4b58533e02d01e806d8608e2c" - dependencies: - inherits "^2.0.1" - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.0.8" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.8.tgz#c8fa3b1b7585bb7ba77c5560b60996ddec6d5309" - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a" - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd" - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" - -browserify-zlib@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" - dependencies: - pako "~0.2.0" - -browserstack@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/browserstack/-/browserstack-1.5.0.tgz#b565425ad62ed72c1082a1eb979d5313c7d4754f" - dependencies: - https-proxy-agent "1.0.0" - -browserstacktunnel-wrapper@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.1.tgz#ffe1910d6e39fe86618183e826690041af53edae" - dependencies: - https-proxy-agent "^1.0.0" - unzip "~0.1.9" - -buffer-crc32@^0.2.1, buffer-crc32@~0.2.1: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - -buffer-shims@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - -buffer@^4.3.0, buffer@^4.9.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -buffers@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" - -builtin-modules@^1.0.0, builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - -bytes@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" - -bytes@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.1.0.tgz#ac93c410e2ffc9cc7cf4b464b38289067f5e47b4" - -bytes@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.2.0.tgz#fd35464a403f6f9117c2de3609ecff9cae000588" - -bytes@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" - -bytes@2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a" - -cache-base@^0.8.4: - version "0.8.5" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-0.8.5.tgz#60ceb3504021eceec7011fd3384b7f4e95729bfa" - dependencies: - collection-visit "^0.2.1" - component-emitter "^1.2.1" - get-value "^2.0.5" - has-value "^0.3.1" - isobject "^3.0.0" - lazy-cache "^2.0.1" - set-value "^0.4.2" - to-object-path "^0.3.0" - union-value "^0.2.3" - unset-value "^0.1.1" - -caller-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" - dependencies: - callsites "^0.2.0" - -callsite@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" - -callsites@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" - -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - -camelcase@^1.0.2, camelcase@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" - -camelcase@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - -camelcase@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - -caseless@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - -caseless@~0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.8.0.tgz#5bca2881d41437f54b2407ebe34888c7b9ad4f7d" - -ccount@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.2.tgz#53b6a2f815bb77b9c2871f7b9a72c3a25f1d8e89" - -center-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" - -chai-nightwatch@~0.1.x: - version "0.1.1" - resolved "https://registry.yarnpkg.com/chai-nightwatch/-/chai-nightwatch-0.1.1.tgz#1ca56de768d3c0868fe7fc2f4d32c2fe894e6be9" - dependencies: - assertion-error "1.0.0" - deep-eql "0.1.3" - -chai@^3.3.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" - dependencies: - assertion-error "^1.0.1" - deep-eql "^0.1.3" - type-detect "^1.0.0" - -chainsaw@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" - dependencies: - traverse ">=0.3.0 <0.4" - -chalk@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" - dependencies: - ansi-styles "^1.1.0" - escape-string-regexp "^1.0.0" - has-ansi "^0.1.0" - strip-ansi "^0.3.0" - supports-color "^0.2.0" - -chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" - dependencies: - ansi-styles "^3.1.0" - escape-string-regexp "^1.0.5" - supports-color "^4.0.0" - -character-entities-html4@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.1.tgz#359a2a4a0f7e29d3dc2ac99bdbe21ee39438ea50" - -character-entities-legacy@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.1.tgz#f40779df1a101872bb510a3d295e1fccf147202f" - -character-entities@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.1.tgz#f76871be5ef66ddb7f8f8e3478ecc374c27d6dca" - -character-reference-invalid@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.1.tgz#942835f750e4ec61a308e60c2ef8cc1011202efc" - -chokidar@^1.0.0, chokidar@^1.2.0, chokidar@^1.4.1, chokidar@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" - dependencies: - anymatch "^1.3.0" - async-each "^1.0.0" - glob-parent "^2.0.0" - inherits "^2.0.1" - is-binary-path "^1.0.0" - is-glob "^2.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.0.0" - optionalDependencies: - fsevents "^1.0.0" - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - -class-utils@^0.3.4: - version "0.3.5" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.5.tgz#17e793103750f9627b2176ea34cfd1b565903c80" - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - lazy-cache "^2.0.2" - static-extend "^0.1.1" - -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - dependencies: - restore-cursor "^2.0.0" - -cli-width@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-1.1.1.tgz#a4d293ef67ebb7b88d4a4d42c0ccf00c4d1e366d" - -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - -cliui@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" - dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" - -cliui@^3.0.3, cliui@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - -clone-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" - -clone-stats@^0.0.1, clone-stats@~0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" - -clone-stats@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" - -clone@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" - -clone@^1.0.0, clone@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" - -clone@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" - -cloneable-readable@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.0.0.tgz#a6290d413f217a61232f95e458ff38418cfb0117" - dependencies: - inherits "^2.0.1" - process-nextick-args "^1.0.6" - through2 "^2.0.1" - -co@^4.5.4, co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - -co@~3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/co/-/co-3.0.6.tgz#1445f226c5eb956138e68c9ac30167ea7d2e6bda" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - -collapse-white-space@^1.0.0, collapse-white-space@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.3.tgz#4b906f670e5a963a87b76b0e1689643341b6023c" - -collection-visit@^0.2.1: - version "0.2.3" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-0.2.3.tgz#2f62483caecc95f083b9a454a3ee9e6139ad7957" - dependencies: - lazy-cache "^2.0.1" - map-visit "^0.1.5" - object-visit "^0.3.4" - -color-convert@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" - dependencies: - color-name "^1.1.1" - -color-name@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - -colors@^1.1.0, colors@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" - -combine-lists@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/combine-lists/-/combine-lists-1.0.1.tgz#458c07e09e0d900fc28b70a3fec2dacd1d2cb7f6" - dependencies: - lodash "^4.5.0" - -combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" - dependencies: - delayed-stream "~1.0.0" - -combined-stream@~0.0.4, combined-stream@~0.0.5: - version "0.0.7" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-0.0.7.tgz#0137e657baa5a7541c57ac37ac5fc07d73b4dc1f" - dependencies: - delayed-stream "0.0.5" - -comma-separated-tokens@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.4.tgz#72083e58d4a462f01866f6617f4d98a3cd3b8a46" - dependencies: - trim "0.0.1" - -commander@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" - -commander@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" - -commander@2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - dependencies: - graceful-readlink ">= 1.0.0" - -commander@^2.9.0, commander@~2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - -component-bind@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" - -component-emitter@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3" - -component-emitter@1.2.1, component-emitter@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - -component-inherit@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" - -compress-commons@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-1.2.0.tgz#58587092ef20d37cb58baf000112c9278ff73b9f" - dependencies: - buffer-crc32 "^0.2.1" - crc32-stream "^2.0.0" - normalize-path "^2.0.0" - readable-stream "^2.0.0" - -compress-commons@~0.2.0: - version "0.2.9" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-0.2.9.tgz#422d927430c01abd06cd455b6dfc04cb4cf8003c" - dependencies: - buffer-crc32 "~0.2.1" - crc32-stream "~0.3.1" - node-int64 "~0.3.0" - readable-stream "~1.0.26" - -compressible@~2.0.5: - version "2.0.11" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.11.tgz#16718a75de283ed8e604041625a2064586797d8a" - dependencies: - mime-db ">= 1.29.0 < 2" - -compression@~1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.5.2.tgz#b03b8d86e6f8ad29683cba8df91ddc6ffc77b395" - dependencies: - accepts "~1.2.12" - bytes "2.1.0" - compressible "~2.0.5" - debug "~2.2.0" - on-headers "~1.0.0" - vary "~1.0.1" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - -concat-stream@^1.5.0, concat-stream@^1.5.1, concat-stream@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" - dependencies: - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -concat-stream@~1.5.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266" - dependencies: - inherits "~2.0.1" - readable-stream "~2.0.0" - typedarray "~0.0.5" - -concat-with-sourcemaps@*, concat-with-sourcemaps@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.0.4.tgz#f55b3be2aeb47601b10a2d5259ccfb70fd2f1dd6" - dependencies: - source-map "^0.5.1" - -connect-livereload@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/connect-livereload/-/connect-livereload-0.5.4.tgz#80157d1371c9f37cc14039ab1895970d119dc3bc" - -connect-timeout@~1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/connect-timeout/-/connect-timeout-1.6.2.tgz#de9a5ec61e33a12b6edaab7b5f062e98c599b88e" - dependencies: - debug "~2.2.0" - http-errors "~1.3.1" - ms "0.7.1" - on-headers "~1.0.0" - -connect@^2.30.0: - version "2.30.2" - resolved "https://registry.yarnpkg.com/connect/-/connect-2.30.2.tgz#8da9bcbe8a054d3d318d74dfec903b5c39a1b609" - dependencies: - basic-auth-connect "1.0.0" - body-parser "~1.13.3" - bytes "2.1.0" - compression "~1.5.2" - connect-timeout "~1.6.2" - content-type "~1.0.1" - cookie "0.1.3" - cookie-parser "~1.3.5" - cookie-signature "1.0.6" - csurf "~1.8.3" - debug "~2.2.0" - depd "~1.0.1" - errorhandler "~1.4.2" - express-session "~1.11.3" - finalhandler "0.4.0" - fresh "0.3.0" - http-errors "~1.3.1" - method-override "~2.3.5" - morgan "~1.6.1" - multiparty "3.3.2" - on-headers "~1.0.0" - parseurl "~1.3.0" - pause "0.1.0" - qs "4.0.0" - response-time "~2.3.1" - serve-favicon "~2.3.0" - serve-index "~1.7.2" - serve-static "~1.10.0" - type-is "~1.6.6" - utils-merge "1.0.0" - vhost "~3.0.1" - -connect@^3.6.0: - version "3.6.3" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.3.tgz#f7320d46a25b4be7b483a2236517f24b1e27e301" - dependencies: - debug "2.6.8" - finalhandler "1.0.4" - parseurl "~1.3.1" - utils-merge "1.0.0" - -console-browserify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - dependencies: - date-now "^0.1.4" - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - -contains-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" - -content-type@~1.0.1, content-type@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" - -continuable-cache@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" - -convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.1.1, convert-source-map@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" - -cookie-parser@~1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.3.5.tgz#9d755570fb5d17890771227a02314d9be7cf8356" - dependencies: - cookie "0.1.3" - cookie-signature "1.0.6" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - -cookie@0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.1.3.tgz#e734a5c1417fce472d5aef82c381cabb64d1a435" - -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - -core-js@^2.2.0, core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - -coveralls@^2.11.11: - version "2.13.1" - resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.13.1.tgz#d70bb9acc1835ec4f063ff9dac5423c17b11f178" - dependencies: - js-yaml "3.6.1" - lcov-parse "0.0.10" - log-driver "1.2.5" - minimist "1.2.0" - request "2.79.0" - -crc32-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-2.0.0.tgz#e3cdd3b4df3168dd74e3de3fbbcb7b297fe908f4" - dependencies: - crc "^3.4.4" - readable-stream "^2.0.0" - -crc32-stream@~0.3.1: - version "0.3.4" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-0.3.4.tgz#73bc25b45fac1db6632231a7bfce8927e9f06552" - dependencies: - buffer-crc32 "~0.2.1" - readable-stream "~1.0.24" - -crc@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/crc/-/crc-3.3.0.tgz#fa622e1bc388bf257309082d6b65200ce67090ba" - -crc@^3.4.4: - version "3.4.4" - resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b" - -create-ecdh@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" - dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" - -create-hash@^1.1.0, create-hash@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - ripemd160 "^2.0.0" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.6" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06" - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-spawn@^5.0.1, cross-spawn@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - -cryptiles@0.2.x: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-0.2.2.tgz#ed91ff1f17ad13d3748288594f8a48a0d26f325c" - dependencies: - boom "0.4.x" - -cryptiles@2.x.x: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" - dependencies: - boom "2.x.x" - -crypto-browserify@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.3.0.tgz#b9fc75bb4a0ed61dcf1cd5dae96eb30c9c3e506c" - dependencies: - browserify-aes "0.4.0" - pbkdf2-compat "2.0.1" - ripemd160 "0.2.0" - sha.js "2.2.6" - -crypto-browserify@^3.11.0: - version "3.11.1" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.1.tgz#948945efc6757a400d6e5e5af47194d10064279f" - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - -csrf@~3.0.0: - version "3.0.6" - resolved "https://registry.yarnpkg.com/csrf/-/csrf-3.0.6.tgz#b61120ddceeafc91e76ed5313bb5c0b2667b710a" - dependencies: - rndm "1.2.0" - tsscmp "1.0.5" - uid-safe "2.1.4" - -css-loader@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.9.1.tgz#2e1aa00ce7e30ef2c6a7a4b300a080a7c979e0dc" - dependencies: - csso "1.3.x" - loader-utils "~0.2.2" - source-map "~0.1.38" - -css-parse@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4" - dependencies: - css "^2.0.0" - -css-value@~0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/css-value/-/css-value-0.0.1.tgz#5efd6c2eea5ea1fd6b6ac57ec0427b18452424ea" - -css@2.X, css@^2.0.0, css@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/css/-/css-2.2.1.tgz#73a4c81de85db664d4ee674f7d47085e3b2d55dc" - dependencies: - inherits "^2.0.1" - source-map "^0.1.38" - source-map-resolve "^0.3.0" - urix "^0.1.0" - -csso@1.3.x: - version "1.3.12" - resolved "https://registry.yarnpkg.com/csso/-/csso-1.3.12.tgz#fc628694a2d38938aaac4996753218fd311cdb9e" - -csurf@~1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/csurf/-/csurf-1.8.3.tgz#23f2a13bf1d8fce1d0c996588394442cba86a56a" - dependencies: - cookie "0.1.3" - cookie-signature "1.0.6" - csrf "~3.0.0" - http-errors "~1.3.1" - -ctype@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f" - -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - dependencies: - array-find-index "^1.0.1" - -custom-event@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" - -d@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" - dependencies: - es5-ext "^0.10.9" - -dargs@christian-bromann/dargs: - version "4.0.1" - resolved "https://codeload.github.com/christian-bromann/dargs/tar.gz/7d6d4164a7c4106dbd14ef39ed8d95b7b5e9b770" - dependencies: - number-is-nan "^1.0.0" - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - dependencies: - assert-plus "^1.0.0" - -data-uri-to-buffer@1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835" - -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" - -dateformat@^1.0.7-1.2.3: - version "1.0.12" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" - dependencies: - get-stdin "^4.0.1" - meow "^3.3.0" - -dateformat@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.0.0.tgz#2743e3abb5c3fc2462e527dca445e04e9f4dee17" - -debug-fabulous@>=0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/debug-fabulous/-/debug-fabulous-0.1.1.tgz#1b970878c9fa4fbd1c88306eab323c830c58f1d6" - dependencies: - debug "2.3.0" - memoizee "^0.4.5" - object-assign "4.1.0" - -debug@2, debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.3, debug@^2.6.8, debug@~2.6.7: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" - dependencies: - ms "2.0.0" - -debug@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.0.0.tgz#89bd9df6732b51256bc6705342bba02ed12131ef" - dependencies: - ms "0.6.2" - -debug@2.2.0, debug@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" - dependencies: - ms "0.7.1" - -debug@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.0.tgz#3912dc55d7167fc3af17d2b85c13f93deaedaa43" - dependencies: - ms "0.7.2" - -debug@2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c" - dependencies: - ms "0.7.2" - -debug@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" - dependencies: - ms "2.0.0" - -decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - -deep-eql@0.1.3, deep-eql@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" - dependencies: - type-detect "0.1.1" - -deep-extend@~0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - -deepmerge@^0.2.7, deepmerge@~0.2.7: - version "0.2.10" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-0.2.10.tgz#8906bf9e525a4fbf1b203b2afcb4640249821219" - -default-require-extensions@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" - dependencies: - strip-bom "^2.0.0" - -defaults@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - dependencies: - clone "^1.0.2" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - dependencies: - is-descriptor "^1.0.0" - -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - -degenerator@~1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095" - dependencies: - ast-types "0.x.x" - escodegen "1.x.x" - esprima "3.x.x" - -del@^2.0.2, del@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" - dependencies: - globby "^5.0.0" - is-path-cwd "^1.0.0" - is-path-in-cwd "^1.0.0" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - rimraf "^2.2.8" - -delayed-stream@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-0.0.5.tgz#d4b1f43a93e8296dfe02694f4680bc37a313c73f" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - -depd@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" - -depd@1.1.1, depd@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" - -depd@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.0.1.tgz#80aec64c9d6d97e65cc2a9caa93c0aa6abf73aaa" - -deprecated@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/deprecated/-/deprecated-0.0.1.tgz#f9c9af5464afa1e7a971458a8bdef2aa94d5bb19" - -des.js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - -detab@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.1.tgz#531f5e326620e2fd4f03264a905fb3bcc8af4df4" - dependencies: - repeat-string "^1.5.4" - -detect-file@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-0.1.0.tgz#4935dedfd9488648e006b0129566e9386711ea63" - dependencies: - fs-exists-sync "^0.1.0" - -detect-indent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" - dependencies: - repeating "^2.0.0" - -detect-newline@2.X: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" - -detective@^4.0.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.5.0.tgz#6e5a8c6b26e6c7a254b1c6b6d7490d98ec91edd1" - dependencies: - acorn "^4.0.3" - defined "^1.0.0" - -di@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" - -diff@1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/diff/-/diff-1.0.8.tgz#343276308ec991b7bc82267ed55bc1411f971666" - -diff@1.4.0, diff@^1.3.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" - -diffie-hellman@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -disparity@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/disparity/-/disparity-2.0.0.tgz#57ddacb47324ae5f58d2cc0da886db4ce9eeb718" - dependencies: - ansi-styles "^2.0.1" - diff "^1.3.2" - -doctrine-temporary-fork@2.0.0-alpha-allowarrayindex: - version "2.0.0-alpha-allowarrayindex" - resolved "https://registry.yarnpkg.com/doctrine-temporary-fork/-/doctrine-temporary-fork-2.0.0-alpha-allowarrayindex.tgz#40015a867eb27e75b26c828b71524f137f89f9f0" - dependencies: - esutils "^2.0.2" - isarray "^1.0.0" - -doctrine@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" - dependencies: - esutils "^2.0.2" - isarray "^1.0.0" - -doctrine@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" - dependencies: - esutils "^2.0.2" - isarray "^1.0.0" - -documentation@^5.2.2: - version "5.3.0" - resolved "https://registry.yarnpkg.com/documentation/-/documentation-5.3.0.tgz#6973aa40bd75f065e7def1df51dbbab86637cfa4" - dependencies: - ansi-html "^0.0.7" - babel-core "^6.17.0" - babel-generator "^6.25.0" - babel-plugin-system-import-transformer "3.1.0" - babel-plugin-transform-decorators-legacy "^1.3.4" - babel-preset-es2015 "^6.16.0" - babel-preset-react "^6.16.0" - babel-preset-stage-0 "^6.16.0" - babel-traverse "^6.16.0" - babel-types "^6.16.0" - babelify "^7.3.0" - babylon "^6.17.2" - chalk "^2.0.0" - chokidar "^1.2.0" - concat-stream "^1.5.0" - disparity "^2.0.0" - doctrine-temporary-fork "2.0.0-alpha-allowarrayindex" - get-port "^3.1.0" - git-url-parse "^6.0.1" - github-slugger "1.1.3" - glob "^7.0.0" - globals-docs "^2.3.0" - highlight.js "^9.1.0" - js-yaml "^3.8.4" - lodash "^4.11.1" - mdast-util-inject "^1.1.0" - micromatch "^3.0.0" - mime "^1.3.4" - module-deps-sortable "4.0.6" - parse-filepath "^1.0.1" - pify "^3.0.0" - read-pkg-up "^2.0.0" - remark "^8.0.0" - remark-html "6.0.1" - remark-toc "^4.0.0" - remote-origin-url "0.4.0" - shelljs "^0.7.5" - stream-array "^1.1.0" - strip-json-comments "^2.0.0" - tiny-lr "^1.0.3" - unist-builder "^1.0.0" - unist-util-visit "^1.0.1" - vfile "^2.0.0" - vfile-reporter "^4.0.0" - vfile-sort "^2.0.0" - vinyl "^2.0.0" - vinyl-fs "^2.3.1" - yargs "^6.0.1" - -dom-serialize@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" - dependencies: - custom-event "~1.0.0" - ent "~2.2.0" - extend "^3.0.0" - void-elements "^2.0.0" - -domain-browser@^1.1.1: - version "1.1.7" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" - -duplexer2@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" - dependencies: - readable-stream "~1.1.9" - -duplexer2@^0.1.2, duplexer2@~0.1.0: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - dependencies: - readable-stream "^2.0.2" - -duplexer@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - -duplexify@^3.2.0, duplexify@^3.5.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.1.tgz#4e1516be68838bc90a49994f0b39a6e5960befcd" - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -ecc-jsbn@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" - dependencies: - jsbn "~0.1.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - -ejs@0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.3.tgz#db8aac47ff80a7df82b4c82c126fe8970870626f" - -ejs@^2.3.1, ejs@^2.5.1: - version "2.5.7" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" - -elliptic@^6.0.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" - -"emoji-regex@>=6.0.0 <=6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" - -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - -encodeurl@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" - -end-of-stream@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206" - dependencies: - once "^1.4.0" - -end-of-stream@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf" - dependencies: - once "~1.3.0" - -engine.io-client@1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.3.tgz#1798ed93451246453d4c6f635d7a201fe940d5ab" - dependencies: - component-emitter "1.2.1" - component-inherit "0.0.3" - debug "2.3.3" - engine.io-parser "1.3.2" - has-cors "1.1.0" - indexof "0.0.1" - parsejson "0.0.3" - parseqs "0.0.5" - parseuri "0.0.5" - ws "1.1.2" - xmlhttprequest-ssl "1.5.3" - yeast "0.1.2" - -engine.io-parser@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.2.tgz#937b079f0007d0893ec56d46cb220b8cb435220a" - dependencies: - after "0.8.2" - arraybuffer.slice "0.0.6" - base64-arraybuffer "0.1.5" - blob "0.0.4" - has-binary "0.1.7" - wtf-8 "1.0.0" - -engine.io@1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.3.tgz#8de7f97895d20d39b85f88eeee777b2bd42b13d4" - dependencies: - accepts "1.3.3" - base64id "1.0.0" - cookie "0.3.1" - debug "2.3.3" - engine.io-parser "1.3.2" - ws "1.1.2" - -enhanced-resolve@^3.4.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - object-assign "^4.0.1" - tapable "^0.2.7" - -enhanced-resolve@~0.9.0: - version "0.9.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e" - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.2.0" - tapable "^0.1.8" - -ent@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - -errno@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" - dependencies: - prr "~0.0.0" - -error-ex@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" - dependencies: - is-arrayish "^0.2.1" - -error@^7.0.0: - version "7.0.2" - resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02" - dependencies: - string-template "~0.2.1" - xtend "~4.0.0" - -errorhandler@~1.4.2: - version "1.4.3" - resolved "https://registry.yarnpkg.com/errorhandler/-/errorhandler-1.4.3.tgz#b7b70ed8f359e9db88092f2d20c0f831420ad83f" - dependencies: - accepts "~1.3.0" - escape-html "~1.0.3" - -es5-ext@^0.10.14, es5-ext@^0.10.30, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2: - version "0.10.30" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.30.tgz#7141a16836697dbabfaaaeee41495ce29f52c939" - dependencies: - es6-iterator "2" - es6-symbol "~3.1" - -es5-shim@^4.0.5, es5-shim@^4.5.2: - version "4.5.9" - resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.5.9.tgz#2a1e2b9e583ff5fed0c20a3ee2cbf3f75230a5c0" - -es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512" - dependencies: - d "1" - es5-ext "^0.10.14" - es6-symbol "^3.1" - -es6-map@^0.1.3: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-set "~0.1.5" - es6-symbol "~3.1.1" - event-emitter "~0.3.5" - -es6-set@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-symbol "3.1.1" - event-emitter "~0.3.5" - -es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1, es6-symbol@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" - dependencies: - d "1" - es5-ext "~0.10.14" - -es6-weak-map@^2.0.1, es6-weak-map@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" - dependencies: - d "1" - es5-ext "^0.10.14" - es6-iterator "^2.0.1" - es6-symbol "^3.1.1" - -escape-html@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.2.tgz#d77d32fa98e38c2f41ae85e9278e0e0e6ba1022c" - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - -escape-string-regexp@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" - -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - -escodegen@1.8.x, escodegen@1.x.x: - version "1.8.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" - dependencies: - esprima "^2.7.1" - estraverse "^1.9.1" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.2.0" - -escope@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" - dependencies: - es6-map "^0.1.3" - es6-weak-map "^2.0.1" - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-config-standard@^10.2.1: - version "10.2.1" - resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz#c061e4d066f379dc17cd562c64e819b4dd454591" - -eslint-import-resolver-node@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz#4422574cde66a9a7b099938ee4d508a199e0e3cc" - dependencies: - debug "^2.6.8" - resolve "^1.2.0" - -eslint-module-utils@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449" - dependencies: - debug "^2.6.8" - pkg-dir "^1.0.0" - -eslint-plugin-import@^2.2.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz#21de33380b9efb55f5ef6d2e210ec0e07e7fa69f" - dependencies: - builtin-modules "^1.1.1" - contains-path "^0.1.0" - debug "^2.6.8" - doctrine "1.5.0" - eslint-import-resolver-node "^0.3.1" - eslint-module-utils "^2.1.1" - has "^1.0.1" - lodash.cond "^4.3.0" - minimatch "^3.0.3" - read-pkg-up "^2.0.0" - -eslint-plugin-node@^5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-5.1.1.tgz#a7ed956e780c22aef6afd1116005acd82f26eac6" - dependencies: - ignore "^3.3.3" - minimatch "^3.0.4" - resolve "^1.3.3" - semver "5.3.0" - -eslint-plugin-promise@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.5.0.tgz#78fbb6ffe047201627569e85a6c5373af2a68fca" - -eslint-plugin-standard@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz#34d0c915b45edc6f010393c7eef3823b08565cf2" - -eslint-scope@^3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint@^4.0.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.6.1.tgz#ddc7fc7fd70bf93205b0b3449bb16a1e9e7d4950" - dependencies: - ajv "^5.2.0" - babel-code-frame "^6.22.0" - chalk "^2.1.0" - concat-stream "^1.6.0" - cross-spawn "^5.1.0" - debug "^2.6.8" - doctrine "^2.0.0" - eslint-scope "^3.7.1" - espree "^3.5.0" - esquery "^1.0.0" - estraverse "^4.2.0" - esutils "^2.0.2" - file-entry-cache "^2.0.0" - functional-red-black-tree "^1.0.1" - glob "^7.1.2" - globals "^9.17.0" - ignore "^3.3.3" - imurmurhash "^0.1.4" - inquirer "^3.0.6" - is-resolvable "^1.0.0" - js-yaml "^3.9.1" - json-stable-stringify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.4" - minimatch "^3.0.2" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - optionator "^0.8.2" - path-is-inside "^1.0.2" - pluralize "^4.0.0" - progress "^2.0.0" - require-uncached "^1.0.3" - semver "^5.3.0" - strip-ansi "^4.0.0" - strip-json-comments "~2.0.1" - table "^4.0.1" - text-table "~0.2.0" - -espree@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.0.tgz#98358625bdd055861ea27e2867ea729faf463d8d" - dependencies: - acorn "^5.1.1" - acorn-jsx "^3.0.0" - -esprima@2.7.x, esprima@^2.6.0, esprima@^2.7.1: - version "2.7.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" - -esprima@3.x.x: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - -esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" - -esquery@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" - dependencies: - estraverse "^4.0.0" - -esrecurse@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" - dependencies: - estraverse "^4.1.0" - object-assign "^4.0.1" - -estraverse@^1.9.1: - version "1.9.3" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" - -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" - -estree-walker@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.3.1.tgz#e6b1a51cf7292524e7237c312e5fe6660c1ce1aa" - -esutils@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" - -etag@~1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8" - -event-emitter@^0.3.5, event-emitter@~0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - dependencies: - d "1" - es5-ext "~0.10.14" - -event-stream@*, event-stream@^3.3.2: - version "3.3.4" - resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" - dependencies: - duplexer "~0.1.1" - from "~0" - map-stream "~0.1.0" - pause-stream "0.0.11" - split "0.3" - stream-combiner "~0.0.4" - through "~2.3.1" - -event-stream@~3.0.18: - version "3.0.20" - resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.0.20.tgz#038bbb2ea9ea90385b26fbc1854d0b539f2abea3" - dependencies: - duplexer "~0.1.1" - from "~0" - map-stream "~0.0.3" - pause-stream "0.0.11" - split "0.2" - stream-combiner "~0.0.3" - through "~2.3.1" - -eventemitter3@1.x.x: - version "1.2.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" - -events@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -expand-braces@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/expand-braces/-/expand-braces-0.1.2.tgz#488b1d1d2451cb3d3a6b192cfc030f44c5855fea" - dependencies: - array-slice "^0.2.3" - array-unique "^0.2.1" - braces "^0.1.2" - -expand-brackets@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" - dependencies: - is-posix-bracket "^0.1.0" - -expand-brackets@^2.0.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-range@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-0.1.1.tgz#4cb8eda0993ca56fa4f41fc42f3cbb4ccadff044" - dependencies: - is-number "^0.1.1" - repeat-string "^0.2.2" - -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - dependencies: - fill-range "^2.1.0" - -expand-tilde@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" - dependencies: - os-homedir "^1.0.1" - -expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - dependencies: - homedir-polyfill "^1.0.1" - -expect.js@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/expect.js/-/expect.js-0.3.1.tgz#b0a59a0d2eff5437544ebf0ceaa6015841d09b5b" - -express-session@~1.11.3: - version "1.11.3" - resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.11.3.tgz#5cc98f3f5ff84ed835f91cbf0aabd0c7107400af" - dependencies: - cookie "0.1.3" - cookie-signature "1.0.6" - crc "3.3.0" - debug "~2.2.0" - depd "~1.0.1" - on-headers "~1.0.0" - parseurl "~1.3.0" - uid-safe "~2.0.0" - utils-merge "1.0.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - dependencies: - is-extendable "^0.1.0" - -extend@3, extend@^3.0.0, extend@~3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" - -external-editor@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.4.tgz#1ed9199da9cbfe2ef2f7a31b2fde8b0d12368972" - dependencies: - iconv-lite "^0.4.17" - jschardet "^1.4.2" - tmp "^0.0.31" - -extglob@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" - dependencies: - is-extglob "^1.0.0" - -extglob@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-1.1.0.tgz#0678b4e2ce45c0e4e50f5e5eafb1b0dab5b4e424" - dependencies: - array-unique "^0.3.2" - define-property "^0.2.5" - expand-brackets "^2.0.1" - extend-shallow "^2.0.1" - fragment-cache "^0.2.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^2.1.0" - -extsprintf@1.3.0, extsprintf@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - -faker@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/faker/-/faker-3.1.0.tgz#0f908faf4e6ec02524e54a57e432c5c013e08c9f" - -fancy-log@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.0.tgz#45be17d02bb9917d60ccffd4995c999e6c8c9948" - dependencies: - chalk "^1.1.1" - time-stamp "^1.0.0" - -fast-deep-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" - -fast-levenshtein@~2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - -faye-websocket@~0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - dependencies: - websocket-driver ">=0.5.1" - -figures@^1.3.5: - version "1.7.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" - dependencies: - escape-string-regexp "^1.0.5" - object-assign "^4.1.0" - -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" - dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" - -file-loader@^0.8.1: - version "0.8.5" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.8.5.tgz#9275d031fe780f27d47f5f4af02bd43713cc151b" - dependencies: - loader-utils "~0.2.5" - -file-uri-to-path@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - -filename-regex@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" - -fileset@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" - dependencies: - glob "^7.0.3" - minimatch "^3.0.3" - -fill-keys@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" - dependencies: - is-object "~1.0.1" - merge-descriptors "~1.0.0" - -fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^1.1.3" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -finalhandler@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.4.0.tgz#965a52d9e8d05d2b857548541fb89b53a2497d9b" - dependencies: - debug "~2.2.0" - escape-html "1.0.2" - on-finished "~2.3.0" - unpipe "~1.0.0" - -finalhandler@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.4.tgz#18574f2e7c4b98b8ae3b230c21f201f31bdb3fb7" - dependencies: - debug "2.6.8" - encodeurl "~1.0.1" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.1" - statuses "~1.3.1" - unpipe "~1.0.0" - -find-cache-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" - dependencies: - commondir "^1.0.1" - make-dir "^1.0.0" - pkg-dir "^2.0.0" - -find-index@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" - -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - -find-up@^2.0.0, find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - dependencies: - locate-path "^2.0.0" - -findup-sync@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" - dependencies: - detect-file "^0.1.0" - is-glob "^2.0.1" - micromatch "^2.3.7" - resolve-dir "^0.1.0" - -fined@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fined/-/fined-1.1.0.tgz#b37dc844b76a2f5e7081e884f7c0ae344f153476" - dependencies: - expand-tilde "^2.0.2" - is-plain-object "^2.0.3" - object.defaults "^1.1.0" - object.pick "^1.2.0" - parse-filepath "^1.0.1" - -first-chunk-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" - -flagged-respawn@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-0.3.2.tgz#ff191eddcd7088a675b2610fffc976be9b8074b5" - -flat-cache@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" - dependencies: - circular-json "^0.3.1" - del "^2.0.2" - graceful-fs "^4.1.2" - write "^0.2.1" - -for-in@^1.0.1, for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - -for-own@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - dependencies: - for-in "^1.0.1" - -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - dependencies: - for-in "^1.0.1" - -foreachasync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/foreachasync/-/foreachasync-3.0.0.tgz#5502987dc8714be3392097f32e0071c9dee07cf6" - -forever-agent@~0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.5.2.tgz#6d0e09c4921f94a27f63d3b49c5feff1ea4c5130" - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - -fork-stream@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/fork-stream/-/fork-stream-0.0.4.tgz#db849fce77f6708a5f8f386ae533a0907b54ae70" - -form-data@~0.1.0: - version "0.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-0.1.4.tgz#91abd788aba9702b1aabfa8bc01031a2ac9e3b12" - dependencies: - async "~0.9.0" - combined-stream "~0.0.4" - mime "~1.2.11" - -form-data@~2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - -formatio@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" - dependencies: - samsam "~1.1" - -fragment-cache@^0.2.0, fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - dependencies: - map-cache "^0.2.2" - -fresh@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f" - -from@~0: - version "0.1.7" - resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" - -fs-access@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a" - dependencies: - null-check "^1.0.0" - -fs-exists-sync@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" - -fs-extra@~0.6.1: - version "0.6.4" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.6.4.tgz#f46f0c75b7841f8d200b3348cd4d691d5a099d15" - dependencies: - jsonfile "~1.0.1" - mkdirp "0.3.x" - ncp "~0.4.2" - rimraf "~2.2.0" - -fs.extra@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fs.extra/-/fs.extra-1.3.2.tgz#dd023f93013bee24531f1b33514c37b20fd93349" - dependencies: - fs-extra "~0.6.1" - mkdirp "~0.3.5" - walk "^2.3.9" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - -fsevents@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" - dependencies: - nan "^2.3.0" - node-pre-gyp "^0.6.36" - -fstream-ignore@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" - dependencies: - fstream "^1.0.0" - inherits "2" - minimatch "^3.0.0" - -"fstream@>= 0.1.30 < 1": - version "0.1.31" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-0.1.31.tgz#7337f058fbbbbefa8c9f561a28cab0849202c988" - dependencies: - graceful-fs "~3.0.2" - inherits "~2.0.0" - mkdirp "0.5" - rimraf "2" - -fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - -ftp@~0.3.10: - version "0.3.10" - resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" - dependencies: - readable-stream "1.1.x" - xregexp "2.0.0" - -function-bind@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gaze@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-0.5.2.tgz#40b709537d24d1d45767db5a908689dfe69ac44f" - dependencies: - globule "~0.1.0" - -generate-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - dependencies: - is-property "^1.0.0" - -get-caller-file@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" - -get-port@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" - -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - -get-uri@2: - version "2.0.1" - resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.1.tgz#dbdcacacd8c608a38316869368117697a1631c59" - dependencies: - data-uri-to-buffer "1" - debug "2" - extend "3" - file-uri-to-path "1" - ftp "~0.3.10" - readable-stream "2" - -get-value@^2.0.3, get-value@^2.0.5, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - dependencies: - assert-plus "^1.0.0" - -git-up@^2.0.0: - version "2.0.8" - resolved "https://registry.yarnpkg.com/git-up/-/git-up-2.0.8.tgz#24be049c9f0b193481d2df4e016a16530a5f4ef4" - dependencies: - is-ssh "^1.3.0" - parse-url "^1.3.0" - -git-url-parse@^6.0.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-6.2.2.tgz#be49024e14b8487553436b4572b8b439532fa871" - dependencies: - git-up "^2.0.0" - -github-slugger@1.1.3, github-slugger@^1.0.0, github-slugger@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.1.3.tgz#314a6e759a18c2b0cc5760d512ccbab549c549a7" - dependencies: - emoji-regex ">=6.0.0 <=6.1.1" - -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - dependencies: - is-glob "^2.0.0" - -glob-parent@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-stream@^3.1.5: - version "3.1.18" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b" - dependencies: - glob "^4.3.1" - glob2base "^0.0.12" - minimatch "^2.0.1" - ordered-read-streams "^0.1.0" - through2 "^0.6.1" - unique-stream "^1.0.0" - -glob-stream@^5.3.2: - version "5.3.5" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22" - dependencies: - extend "^3.0.0" - glob "^5.0.3" - glob-parent "^3.0.0" - micromatch "^2.3.7" - ordered-read-streams "^0.3.0" - through2 "^0.6.0" - to-absolute-glob "^0.1.1" - unique-stream "^2.0.2" - -glob-watcher@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b" - dependencies: - gaze "^0.5.1" - -glob2base@^0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" - dependencies: - find-index "^0.1.1" - -glob@3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.3.tgz#e313eeb249c7affaa5c475286b0e115b59839467" - dependencies: - graceful-fs "~2.0.0" - inherits "2" - minimatch "~0.2.11" - -glob@7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.5.tgz#b4202a69099bbb4d292a7c1b95b6682b67ebdc95" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^4.3.1: - version "4.5.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "^2.0.1" - once "^1.3.0" - -glob@^5.0.10, glob@^5.0.15, glob@^5.0.3: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@~3.1.21: - version "3.1.21" - resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd" - dependencies: - graceful-fs "~1.2.0" - inherits "1" - minimatch "~0.2.11" - -glob@~4.3.0: - version "4.3.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-4.3.5.tgz#80fbb08ca540f238acce5d11d1e9bc41e75173d3" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "^2.0.1" - once "^1.3.0" - -global-modules@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" - dependencies: - global-prefix "^0.1.4" - is-windows "^0.2.0" - -global-prefix@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" - dependencies: - homedir-polyfill "^1.0.0" - ini "^1.3.4" - is-windows "^0.2.0" - which "^1.2.12" - -globals-docs@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/globals-docs/-/globals-docs-2.3.0.tgz#dca4088af196f7800f4eba783eaeff335cb6759c" - -globals@^9.17.0, globals@^9.18.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - -globby@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" - dependencies: - array-union "^1.0.1" - arrify "^1.0.0" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -globule@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/globule/-/globule-0.1.0.tgz#d9c8edde1da79d125a151b79533b978676346ae5" - dependencies: - glob "~3.1.21" - lodash "~1.0.1" - minimatch "~0.2.11" - -glogg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5" - dependencies: - sparkles "^1.0.0" - -graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.0, graceful-fs@^4.1.2: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - -graceful-fs@^3.0.0, graceful-fs@~3.0.2: - version "3.0.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" - dependencies: - natives "^1.1.0" - -graceful-fs@~1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" - -graceful-fs@~2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-2.0.3.tgz#7cd2cdb228a4a3f36e95efa6cc142de7d1a136d0" - -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - -growl@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.8.1.tgz#4b2dec8d907e93db336624dcec0183502f8c9428" - -growl@1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" - -gulp-babel@^6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/gulp-babel/-/gulp-babel-6.1.2.tgz#7c0176e4ba3f244c60588a0c4b320a45d1adefce" - dependencies: - babel-core "^6.0.2" - gulp-util "^3.0.0" - object-assign "^4.0.1" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl-sourcemaps-apply "^0.2.0" - -gulp-clean@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/gulp-clean/-/gulp-clean-0.3.2.tgz#a347d473acea40182f935587a451941671928102" - dependencies: - gulp-util "^2.2.14" - rimraf "^2.2.8" - through2 "^0.4.2" - -gulp-concat@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/gulp-concat/-/gulp-concat-2.6.1.tgz#633d16c95d88504628ad02665663cee5a4793353" - dependencies: - concat-with-sourcemaps "^1.0.0" - through2 "^2.0.0" - vinyl "^2.0.0" - -gulp-connect@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/gulp-connect/-/gulp-connect-5.0.0.tgz#f2fdf306ae911468368c2285f2d782f13eddaf4e" - dependencies: - connect "^2.30.0" - connect-livereload "^0.5.4" - event-stream "^3.3.2" - gulp-util "^3.0.6" - tiny-lr "^0.2.1" - -gulp-documentation@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/gulp-documentation/-/gulp-documentation-3.2.1.tgz#af524abfd72e23e7155f00b2a18a07a3642a8dd5" - dependencies: - through2 "^2.0.3" - vinyl "^2.1.0" - -gulp-eslint@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/gulp-eslint/-/gulp-eslint-4.0.0.tgz#16d9ea4d696e7b7a9d65eeb1aa5bc4ba0a22c7f7" - dependencies: - eslint "^4.0.0" - gulp-util "^3.0.8" - -gulp-footer@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/gulp-footer/-/gulp-footer-1.0.5.tgz#e84ca777e266be7bbc2d45d2df0e7eba8dfa3e54" - dependencies: - event-stream "*" - gulp-util "*" - lodash.assign "*" - -gulp-header@^1.7.1: - version "1.8.9" - resolved "https://registry.yarnpkg.com/gulp-header/-/gulp-header-1.8.9.tgz#c9f10fee0632d81e939789c6ecf45a151bf3098b" - dependencies: - concat-with-sourcemaps "*" - gulp-util "*" - object-assign "*" - through2 "^2.0.0" - -gulp-if@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/gulp-if/-/gulp-if-2.0.2.tgz#a497b7e7573005041caa2bc8b7dda3c80444d629" - dependencies: - gulp-match "^1.0.3" - ternary-stream "^2.0.1" - through2 "^2.0.1" - -gulp-match@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/gulp-match/-/gulp-match-1.0.3.tgz#91c7c0d7f29becd6606d57d80a7f8776a87aba8e" - dependencies: - minimatch "^3.0.3" - -gulp-optimize-js@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/gulp-optimize-js/-/gulp-optimize-js-1.1.0.tgz#5fd15c68b36f6e1e7387784f8578435f75696245" - dependencies: - gulp-util "^3.0.7" - lodash "^4.16.2" - optimize-js "^1.0.0" - through2 "^2.0.1" - -gulp-rename@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-1.2.2.tgz#3ad4428763f05e2764dec1c67d868db275687817" - -gulp-replace@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/gulp-replace/-/gulp-replace-0.4.0.tgz#e22bc9c03e9d051b32881cc589bd3e8c4e54168a" - dependencies: - event-stream "~3.0.18" - istextorbinary "~1.0.0" - replacestream "0.1.3" - -gulp-shell@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/gulp-shell/-/gulp-shell-0.5.2.tgz#a4959ca0651ad1c7bbfe70b2d0adbbb4e1aea98d" - dependencies: - async "^1.5.0" - gulp-util "^3.0.7" - lodash "^4.0.0" - through2 "^2.0.0" - -gulp-sourcemaps@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz#b86ff349d801ceb56e1d9e7dc7bbcb4b7dee600c" - dependencies: - convert-source-map "^1.1.1" - graceful-fs "^4.1.2" - strip-bom "^2.0.0" - through2 "^2.0.0" - vinyl "^1.0.0" - -gulp-sourcemaps@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-2.6.1.tgz#833a4e28f0b8f4661075032cd782417f7cd8fb0b" - dependencies: - "@gulp-sourcemaps/identity-map" "1.X" - "@gulp-sourcemaps/map-sources" "1.X" - acorn "4.X" - convert-source-map "1.X" - css "2.X" - debug-fabulous ">=0.1.1" - detect-newline "2.X" - graceful-fs "4.X" - source-map "0.X" - strip-bom-string "1.X" - through2 "2.X" - vinyl "1.X" - -gulp-uglify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.0.tgz#0df0331d72a0d302e3e37e109485dddf33c6d1ca" - dependencies: - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash "^4.13.1" - make-error-cause "^1.1.1" - through2 "^2.0.0" - uglify-js "^3.0.5" - vinyl-sourcemaps-apply "^0.2.0" - -gulp-util@*, gulp-util@^3.0.0, gulp-util@^3.0.4, gulp-util@^3.0.6, gulp-util@^3.0.7, gulp-util@^3.0.8: - version "3.0.8" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" - dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^2.0.0" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" - -gulp-util@^2.2.14: - version "2.2.20" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-2.2.20.tgz#d7146e5728910bd8f047a6b0b1e549bc22dbd64c" - dependencies: - chalk "^0.5.0" - dateformat "^1.0.7-1.2.3" - lodash._reinterpolate "^2.4.1" - lodash.template "^2.4.1" - minimist "^0.2.0" - multipipe "^0.1.0" - through2 "^0.5.0" - vinyl "^0.2.1" - -gulp-webdriver@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/gulp-webdriver/-/gulp-webdriver-1.0.3.tgz#98ce81cf9ae06a7a1907b86d10f69386f4383a2d" - dependencies: - dargs christian-bromann/dargs - deepmerge "^0.2.7" - gulp-util "^3.0.4" - through2 "^0.6.5" - webdriverio "^3.4.0" - -gulp@^3.8.7: - version "3.9.1" - resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4" - dependencies: - archy "^1.0.0" - chalk "^1.0.0" - deprecated "^0.0.1" - gulp-util "^3.0.0" - interpret "^1.0.0" - liftoff "^2.1.0" - minimist "^1.1.0" - orchestrator "^0.3.0" - pretty-hrtime "^1.0.0" - semver "^4.1.0" - tildify "^1.0.0" - v8flags "^2.0.2" - vinyl-fs "^0.3.0" - -gulplog@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" - dependencies: - glogg "^1.0.0" - -handlebars@^4.0.1, handlebars@^4.0.3: - version "4.0.10" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" - dependencies: - async "^1.4.0" - optimist "^0.6.1" - source-map "^0.4.4" - optionalDependencies: - uglify-js "^2.6" - -har-schema@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" - -har-validator@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" - dependencies: - chalk "^1.1.1" - commander "^2.9.0" - is-my-json-valid "^2.12.4" - pinkie-promise "^2.0.0" - -har-validator@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" - dependencies: - ajv "^4.9.1" - har-schema "^1.0.5" - -has-ansi@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" - dependencies: - ansi-regex "^0.2.0" - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - dependencies: - ansi-regex "^2.0.0" - -has-binary@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/has-binary/-/has-binary-0.1.7.tgz#68e61eb16210c9545a0a5cce06a873912fe1e68c" - dependencies: - isarray "0.0.1" - -has-cors@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" - -has-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" - -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" - -has-gulplog@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" - dependencies: - sparkles "^1.0.0" - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - -has@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" - dependencies: - function-bind "^1.0.2" - -hash-base@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" - dependencies: - inherits "^2.0.1" - -hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.0" - -hast-util-is-element@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.0.0.tgz#3f7216978b2ae14d98749878782675f33be3ce00" - -hast-util-sanitize@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-1.1.1.tgz#c439852d9db7ff554ecd6be96435a6a8274ade32" - dependencies: - xtend "^4.0.1" - -hast-util-to-html@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-3.1.0.tgz#882c99849e40130e991c042e456d453d95c36cff" - dependencies: - ccount "^1.0.0" - comma-separated-tokens "^1.0.1" - hast-util-is-element "^1.0.0" - hast-util-whitespace "^1.0.0" - html-void-elements "^1.0.0" - kebab-case "^1.0.0" - property-information "^3.1.0" - space-separated-tokens "^1.0.0" - stringify-entities "^1.0.1" - unist-util-is "^2.0.0" - xtend "^4.0.1" - -hast-util-whitespace@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.0.tgz#bd096919625d2936e1ff17bc4df7fd727f17ece9" - -hawk@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-1.1.1.tgz#87cd491f9b46e4e2aeaca335416766885d2d1ed9" - dependencies: - boom "0.4.x" - cryptiles "0.2.x" - hoek "0.9.x" - sntp "0.2.x" - -hawk@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" - -highlight.js@^9.1.0: - version "9.12.0" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" - -hmac-drbg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hoek@0.9.x: - version "0.9.1" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-0.9.1.tgz#3d322462badf07716ea7eb85baf88079cddce505" - -hoek@2.x.x: - version "2.16.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" - -home-or-tmp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.1" - -homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" - dependencies: - parse-passwd "^1.0.0" - -hosted-git-info@^2.1.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" - -html-void-elements@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.2.tgz#9d22e0ca32acc95b3f45b8d5b4f6fbdc05affd55" - -http-errors@1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" - dependencies: - depd "1.1.0" - inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" - -http-errors@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.3.1.tgz#197e22cdebd4198585e8694ef6786197b91ed942" - dependencies: - inherits "~2.0.1" - statuses "1" - -http-errors@~1.6.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" - dependencies: - depd "1.1.1" - inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" - -http-proxy-agent@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz#cc1ce38e453bf984a0f7702d2dd59c73d081284a" - dependencies: - agent-base "2" - debug "2" - extend "3" - -http-proxy@^1.13.0: - version "1.16.2" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" - dependencies: - eventemitter3 "1.x.x" - requires-port "1.x.x" - -http-signature@~0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-0.10.1.tgz#4fbdac132559aa8323121e540779c0a012b27e66" - dependencies: - asn1 "0.1.11" - assert-plus "^0.1.5" - ctype "0.5.3" - -http-signature@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" - dependencies: - assert-plus "^0.2.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -https-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" - -https-proxy-agent@1, https-proxy-agent@1.0.0, https-proxy-agent@^1.0.0, https-proxy-agent@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz#35f7da6c48ce4ddbfa264891ac593ee5ff8671e6" - dependencies: - agent-base "2" - debug "2" - extend "3" - -iconv-lite@0.4.11: - version "0.4.11" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.11.tgz#2ecb42fd294744922209a2e7c404dac8793d8ade" - -iconv-lite@0.4.13: - version "0.4.13" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" - -iconv-lite@0.4.15: - version "0.4.15" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" - -iconv-lite@0.4.18, iconv-lite@^0.4.17: - version "0.4.18" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" - -ieee754@^1.1.4: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" - -ignore@^3.3.3: - version "3.3.5" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.5.tgz#c4e715455f6073a8d7e5dae72d2fc9d71663dba6" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - dependencies: - repeating "^2.0.0" - -indexof@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" - -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - -ini@^1.3.3, ini@^1.3.4, ini@~1.3.0: - version "1.3.4" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" - -inquirer@^0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.8.5.tgz#dbd740cf6ca3b731296a63ce6f6d961851f336df" - dependencies: - ansi-regex "^1.1.1" - chalk "^1.0.0" - cli-width "^1.0.1" - figures "^1.3.5" - lodash "^3.3.1" - readline2 "^0.1.1" - rx "^2.4.3" - through "^2.3.6" - -inquirer@^3.0.6: - version "3.2.3" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.2.3.tgz#1c7b1731cf77b934ec47d22c9ac5aa8fe7fbe095" - dependencies: - ansi-escapes "^2.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^2.0.4" - figures "^2.0.0" - lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rx-lite "^4.0.8" - rx-lite-aggregates "^4.0.8" - string-width "^2.1.0" - strip-ansi "^4.0.0" - through "^2.3.6" - -interpret@^0.6.4: - version "0.6.6" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-0.6.6.tgz#fecd7a18e7ce5ca6abfb953e1f86213a49f1625b" - -interpret@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" - -invariant@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" - dependencies: - loose-envify "^1.0.0" - -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - -ip@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.0.1.tgz#c7e356cdea225ae71b36d70f2e71a92ba4e42590" - -ip@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - -is-absolute@^0.2.3: - version "0.2.6" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb" - dependencies: - is-relative "^0.2.1" - is-windows "^0.2.0" - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - dependencies: - kind-of "^3.0.2" - -is-alphabetical@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.1.tgz#c77079cc91d4efac775be1034bf2d243f95e6f08" - -is-alphanumeric@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4" - -is-alphanumerical@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.1.tgz#dfb4aa4d1085e33bdb61c2dee9c80e9c6c19f53b" - dependencies: - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - dependencies: - binary-extensions "^1.0.0" - -is-buffer@^1.1.4, is-buffer@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" - -is-builtin-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" - dependencies: - builtin-modules "^1.0.0" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - dependencies: - kind-of "^3.0.2" - -is-decimal@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.1.tgz#f5fb6a94996ad9e8e3761fbfbd091f1fca8c4e82" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.1.tgz#2c6023599bde2de9d5d2c8b9a9d94082036b6ef2" - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-dotfile@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - -is-equal-shallow@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" - dependencies: - is-primitive "^2.0.0" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - -is-finite@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - -is-generator@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-generator/-/is-generator-1.0.3.tgz#c14c21057ed36e328db80347966c693f886389f3" - -is-glob@^2.0.0, is-glob@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - dependencies: - is-extglob "^1.0.0" - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - dependencies: - is-extglob "^2.1.0" - -is-hexadecimal@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69" - -is-my-json-valid@^2.12.4: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - -is-number@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806" - -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - dependencies: - kind-of "^3.0.2" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - dependencies: - kind-of "^3.0.2" - -is-object@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" - -is-odd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-1.0.0.tgz#3b8a932eb028b3775c39bb09e91767accdb69088" - dependencies: - is-number "^3.0.0" - -is-path-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" - -is-path-in-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" - dependencies: - is-path-inside "^1.0.0" - -is-path-inside@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" - dependencies: - path-is-inside "^1.0.1" - -is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - -is-plain-object@^2.0.1, is-plain-object@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - dependencies: - isobject "^3.0.1" - -is-posix-bracket@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" - -is-primitive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" - -is-promise@^2.1, is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - -is-property@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - -is-relative@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5" - dependencies: - is-unc-path "^0.1.1" - -is-resolvable@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" - dependencies: - tryit "^1.0.1" - -is-ssh@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.0.tgz#ebea1169a2614da392a63740366c3ce049d8dff6" - dependencies: - protocols "^1.1.0" - -is-stream@^1.0.1, is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - -is-unc-path@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-0.1.2.tgz#6ab053a72573c10250ff416a3814c35178af39b9" - dependencies: - unc-path-regex "^0.1.0" - -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - -is-valid-glob@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe" - -is-whitespace-character@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.1.tgz#9ae0176f3282b65457a1992cdb084f8a5f833e3b" - -is-windows@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" - -is-word-character@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.1.tgz#5a03fa1ea91ace8a6eb0c7cd770eb86d65c8befb" - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - -isbinaryfile@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.2.tgz#4a3e974ec0cba9004d3fc6cde7209ea69368a621" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - -isobject@^2.0.0, isobject@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - -istanbul-api@^1.1.8: - version "1.1.14" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.14.tgz#25bc5701f7c680c0ffff913de46e3619a3a6e680" - dependencies: - async "^2.1.4" - fileset "^2.0.2" - istanbul-lib-coverage "^1.1.1" - istanbul-lib-hook "^1.0.7" - istanbul-lib-instrument "^1.8.0" - istanbul-lib-report "^1.1.1" - istanbul-lib-source-maps "^1.2.1" - istanbul-reports "^1.1.2" - js-yaml "^3.7.0" - mkdirp "^0.5.1" - once "^1.4.0" - -istanbul-instrumenter-loader@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.0.tgz#9f553923b22360bac95e617aaba01add1f7db0b2" - dependencies: - convert-source-map "^1.5.0" - istanbul-lib-instrument "^1.7.3" - loader-utils "^1.1.0" - schema-utils "^0.3.0" - -istanbul-lib-coverage@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" - -istanbul-lib-hook@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz#dd6607f03076578fe7d6f2a630cf143b49bacddc" - dependencies: - append-transform "^0.4.0" - -istanbul-lib-instrument@^1.7.3, istanbul-lib-instrument@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.8.0.tgz#66f6c9421cc9ec4704f76f2db084ba9078a2b532" - dependencies: - babel-generator "^6.18.0" - babel-template "^6.16.0" - babel-traverse "^6.18.0" - babel-types "^6.18.0" - babylon "^6.18.0" - istanbul-lib-coverage "^1.1.1" - semver "^5.3.0" - -istanbul-lib-report@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#f0e55f56655ffa34222080b7a0cd4760e1405fc9" - dependencies: - istanbul-lib-coverage "^1.1.1" - mkdirp "^0.5.1" - path-parse "^1.0.5" - supports-color "^3.1.2" - -istanbul-lib-source-maps@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz#a6fe1acba8ce08eebc638e572e294d267008aa0c" - dependencies: - debug "^2.6.3" - istanbul-lib-coverage "^1.1.1" - mkdirp "^0.5.1" - rimraf "^2.6.1" - source-map "^0.5.3" - -istanbul-reports@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.2.tgz#0fb2e3f6aa9922bd3ce45d05d8ab4d5e8e07bd4f" - dependencies: - handlebars "^4.0.3" - -istanbul@^0.4.5: - version "0.4.5" - resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" - dependencies: - abbrev "1.0.x" - async "1.x" - escodegen "1.8.x" - esprima "2.7.x" - glob "^5.0.15" - handlebars "^4.0.1" - js-yaml "3.x" - mkdirp "0.5.x" - nopt "3.x" - once "1.x" - resolve "1.1.x" - supports-color "^3.1.0" - which "^1.1.1" - wordwrap "^1.0.0" - -istextorbinary@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-1.0.2.tgz#ace19354d1a9a0173efeb1084ce0f87b0ad7decf" - dependencies: - binaryextensions "~1.0.0" - textextensions "~1.0.0" - -jade@0.26.3: - version "0.26.3" - resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" - dependencies: - commander "0.6.1" - mkdirp "0.3.0" - -js-tokens@^3.0.0, js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - -js-yaml@3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" - dependencies: - argparse "^1.0.7" - esprima "^2.6.0" - -js-yaml@3.x, js-yaml@^3.7.0, js-yaml@^3.8.4, js-yaml@^3.9.1: - version "3.9.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0" - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - -jschardet@^1.4.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.1.tgz#c519f629f86b3a5bedba58a88d311309eec097f9" - -jsesc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - -json-loader@^0.5.1, json-loader@^0.5.4: - version "0.5.7" - resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" - -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - -json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - dependencies: - jsonify "~0.0.0" - -json-stringify-safe@~5.0.0, json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - -json3@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" - -json5@^0.5.0, json5@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - -jsonfile@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-1.0.1.tgz#ea5efe40b83690b98667614a7392fc60e842c0dd" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -karma-babel-preprocessor@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/karma-babel-preprocessor/-/karma-babel-preprocessor-6.0.1.tgz#7ae1d3e64950dbe11f421b74040ab08fb5a66c21" - dependencies: - babel-core "^6.0.0" - -karma-browserstack-launcher@^1.0.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/karma-browserstack-launcher/-/karma-browserstack-launcher-1.3.0.tgz#61fe3d36b1cf10681e40f9d874bf37271fb1c674" - dependencies: - browserstack "1.5.0" - browserstacktunnel-wrapper "~2.0.1" - q "~1.5.0" - -karma-chai@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/karma-chai/-/karma-chai-0.1.0.tgz#bee5ad40400517811ae34bb945f762909108b79a" - -karma-chrome-launcher@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz#cf1b9d07136cc18fe239327d24654c3dbc368acf" - dependencies: - fs-access "^1.0.0" - which "^1.2.1" - -karma-coverage-istanbul-reporter@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-1.3.0.tgz#d142cd9c55731c9e363ef7374e8ef1a31bebfadb" - dependencies: - istanbul-api "^1.1.8" - minimatch "^3.0.4" - -karma-es5-shim@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/karma-es5-shim/-/karma-es5-shim-0.0.4.tgz#cdd00333cce77c2e4ce03e3ac93f2f8ecd1fb952" - dependencies: - es5-shim "^4.0.5" - -karma-expect@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/karma-expect/-/karma-expect-1.1.3.tgz#c6b0a56ff18903db11af4f098cc6e7cf198ce275" - dependencies: - expect.js "^0.3.1" - -karma-firefox-launcher@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-1.0.1.tgz#ce58f47c2013a88156d55a5d61337c099cf5bb51" - -karma-ie-launcher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/karma-ie-launcher/-/karma-ie-launcher-1.0.0.tgz#497986842c490190346cd89f5494ca9830c6d59c" - dependencies: - lodash "^4.6.1" - -karma-mocha@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-1.3.0.tgz#eeaac7ffc0e201eb63c467440d2b69c7cf3778bf" - dependencies: - minimist "1.2.0" - -karma-opera-launcher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/karma-opera-launcher/-/karma-opera-launcher-1.0.0.tgz#fa51628531a1d0be84b2d8dc0d7ee209fc8ff91a" - -karma-requirejs@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/karma-requirejs/-/karma-requirejs-1.1.0.tgz#fddae2cb87d7ebc16fb0222893564d7fee578798" - -karma-safari-launcher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz#96982a2cc47d066aae71c553babb28319115a2ce" - -karma-sauce-launcher@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/karma-sauce-launcher/-/karma-sauce-launcher-1.2.0.tgz#6f2558ddef3cf56879fa27540c8ae9f8bfd16bca" - dependencies: - q "^1.5.0" - sauce-connect-launcher "^1.2.2" - saucelabs "^1.4.0" - wd "^1.4.0" - -karma-script-launcher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/karma-script-launcher/-/karma-script-launcher-1.0.0.tgz#cd017c4de5ef09e5a9da793276176108dd4b542d" - -karma-sinon-ie@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/karma-sinon-ie/-/karma-sinon-ie-2.0.0.tgz#d07f05ac911baea5f6dbc95e1404fd9c93f6cc65" - -karma-sourcemap-loader@^0.3.7: - version "0.3.7" - resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.7.tgz#91322c77f8f13d46fed062b042e1009d4c4505d8" - dependencies: - graceful-fs "^4.1.2" - -karma-spec-reporter@^0.0.31: - version "0.0.31" - resolved "https://registry.yarnpkg.com/karma-spec-reporter/-/karma-spec-reporter-0.0.31.tgz#4830dc7148a155c7d7a186e632339a0d80fadec3" - dependencies: - colors "^1.1.2" - -karma-webpack@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.4.tgz#3e2d4f48ba94a878e1c66bb8e1ae6128987a175b" - dependencies: - async "~0.9.0" - loader-utils "^0.2.5" - lodash "^3.8.0" - source-map "^0.1.41" - webpack-dev-middleware "^1.0.11" - -karma@^1.7.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/karma/-/karma-1.7.1.tgz#85cc08e9e0a22d7ce9cca37c4a1be824f6a2b1ae" - dependencies: - bluebird "^3.3.0" - body-parser "^1.16.1" - chokidar "^1.4.1" - colors "^1.1.0" - combine-lists "^1.0.0" - connect "^3.6.0" - core-js "^2.2.0" - di "^0.0.1" - dom-serialize "^2.2.0" - expand-braces "^0.1.1" - glob "^7.1.1" - graceful-fs "^4.1.2" - http-proxy "^1.13.0" - isbinaryfile "^3.0.0" - lodash "^3.8.0" - log4js "^0.6.31" - mime "^1.3.4" - minimatch "^3.0.2" - optimist "^0.6.1" - qjobs "^1.1.4" - range-parser "^1.2.0" - rimraf "^2.6.0" - safe-buffer "^5.0.1" - socket.io "1.7.3" - source-map "^0.5.3" - tmp "0.0.31" - useragent "^2.1.12" - -kebab-case@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/kebab-case/-/kebab-case-1.0.0.tgz#3f9e4990adcad0c686c0e701f7645868f75f91eb" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.0.2.tgz#f57bec933d9a2209ffa96c5c08343607b7035fda" - -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - -lazy-cache@^2.0.1, lazy-cache@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" - dependencies: - set-getter "^0.1.0" - -lazystream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" - dependencies: - readable-stream "^2.0.5" - -lazystream@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-0.1.0.tgz#1b25d63c772a4c20f0a5ed0a9d77f484b6e16920" - dependencies: - readable-stream "~1.0.2" - -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - dependencies: - invert-kv "^1.0.0" - -lcov-parse@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3" - -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -liftoff@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.3.0.tgz#a98f2ff67183d8ba7cfaca10548bd7ff0550b385" - dependencies: - extend "^3.0.0" - findup-sync "^0.4.2" - fined "^1.0.1" - flagged-respawn "^0.3.2" - lodash.isplainobject "^4.0.4" - lodash.isstring "^4.0.1" - lodash.mapvalues "^4.4.0" - rechoir "^0.6.2" - resolve "^1.1.7" - -livereload-js@^2.2.0, livereload-js@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.2.2.tgz#6c87257e648ab475bc24ea257457edcc1f8d0bc2" - -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - -load-json-file@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - strip-bom "^3.0.0" - -loader-runner@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" - -loader-utils@^0.2.11, loader-utils@^0.2.5, loader-utils@~0.2.2, loader-utils@~0.2.3, loader-utils@~0.2.5: - version "0.2.17" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" - dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" - object-assign "^4.0.1" - -loader-utils@^1.0.2, loader-utils@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" - dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" - -localtunnel@^1.3.0: - version "1.8.3" - resolved "https://registry.yarnpkg.com/localtunnel/-/localtunnel-1.8.3.tgz#dcc5922fd85651037d4bde24fd93248d0b24eb05" - dependencies: - debug "2.6.8" - openurl "1.1.1" - request "2.81.0" - yargs "3.29.0" - -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - -lodash._arraycopy@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz#76e7b7c1f1fb92547374878a562ed06a3e50f6e1" - -lodash._arrayeach@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz#bab156b2a90d3f1bbd5c653403349e5e5933ef9e" - -lodash._baseassign@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" - dependencies: - lodash._basecopy "^3.0.0" - lodash.keys "^3.0.0" - -lodash._baseclone@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz#303519bf6393fe7e42f34d8b630ef7794e3542b7" - dependencies: - lodash._arraycopy "^3.0.0" - lodash._arrayeach "^3.0.0" - lodash._baseassign "^3.0.0" - lodash._basefor "^3.0.0" - lodash.isarray "^3.0.0" - lodash.keys "^3.0.0" - -lodash._baseclone@^4.0.0: - version "4.5.7" - resolved "https://registry.yarnpkg.com/lodash._baseclone/-/lodash._baseclone-4.5.7.tgz#ce42ade08384ef5d62fa77c30f61a46e686f8434" - -lodash._basecopy@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" - -lodash._basecreate@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" - -lodash._basefor@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash._basefor/-/lodash._basefor-3.0.3.tgz#7550b4e9218ef09fad24343b612021c79b4c20c2" - -lodash._basetostring@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" - -lodash._basevalues@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" - -lodash._bindcallback@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" - -lodash._escapehtmlchar@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz#df67c3bb6b7e8e1e831ab48bfa0795b92afe899d" - dependencies: - lodash._htmlescapes "~2.4.1" - -lodash._escapestringchar@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz#ecfe22618a2ade50bfeea43937e51df66f0edb72" - -lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - -lodash._htmlescapes@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz#32d14bf0844b6de6f8b62a051b4f67c228b624cb" - -lodash._isiterateecall@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" - -lodash._isnative@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._isnative/-/lodash._isnative-2.4.1.tgz#3ea6404b784a7be836c7b57580e1cdf79b14832c" - -lodash._objecttypes@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz#7c0b7f69d98a1f76529f890b0cdb1b4dfec11c11" - -lodash._reescape@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" - -lodash._reevaluate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" - -lodash._reinterpolate@^2.4.1, lodash._reinterpolate@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz#4f1227aa5a8711fc632f5b07a1f4607aab8b3222" - -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - -lodash._reunescapedhtml@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz#747c4fc40103eb3bb8a0976e571f7a2659e93ba7" - dependencies: - lodash._htmlescapes "~2.4.1" - lodash.keys "~2.4.1" - -lodash._root@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - -lodash._shimkeys@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz#6e9cc9666ff081f0b5a6c978b83e242e6949d203" - dependencies: - lodash._objecttypes "~2.4.1" - -lodash._stack@^4.0.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/lodash._stack/-/lodash._stack-4.1.3.tgz#751aa76c1b964b047e76d14fc72a093fcb5e2dd0" - -lodash.assign@*, lodash.assign@^4.0.3, lodash.assign@^4.0.6: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" - -lodash.clone@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-3.0.3.tgz#84688c73d32b5a90ca25616963f189252a997043" - dependencies: - lodash._baseclone "^3.0.0" - lodash._bindcallback "^3.0.0" - lodash._isiterateecall "^3.0.0" - -lodash.clone@^4.3.2: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" - -lodash.cond@^4.3.0: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" - -lodash.create@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" - dependencies: - lodash._baseassign "^3.0.0" - lodash._basecreate "^3.0.0" - lodash._isiterateecall "^3.0.0" - -lodash.defaults@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-2.4.1.tgz#a7e8885f05e68851144b6e12a8f3678026bc4c54" - dependencies: - lodash._objecttypes "~2.4.1" - lodash.keys "~2.4.1" - -lodash.defaultsdeep@4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.3.2.tgz#6c1a586e6c5647b0e64e2d798141b8836158be8a" - dependencies: - lodash._baseclone "^4.0.0" - lodash._stack "^4.0.0" - lodash.isplainobject "^4.0.0" - lodash.keysin "^4.0.0" - lodash.mergewith "^4.0.0" - lodash.rest "^4.0.0" - -lodash.escape@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" - dependencies: - lodash._root "^3.0.0" - -lodash.escape@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-2.4.1.tgz#2ce12c5e084db0a57dda5e5d1eeeb9f5d175a3b4" - dependencies: - lodash._escapehtmlchar "~2.4.1" - lodash._reunescapedhtml "~2.4.1" - lodash.keys "~2.4.1" - -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - -lodash.isequal@^4.0.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - -lodash.isobject@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-2.4.1.tgz#5a2e47fe69953f1ee631a7eba1fe64d2d06558f5" - dependencies: - lodash._objecttypes "~2.4.1" - -lodash.isplainobject@^4.0.0, lodash.isplainobject@^4.0.4: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - -lodash.keys@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" - -lodash.keys@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-2.4.1.tgz#48dea46df8ff7632b10d706b8acb26591e2b3727" - dependencies: - lodash._isnative "~2.4.1" - lodash._shimkeys "~2.4.1" - lodash.isobject "~2.4.1" - -lodash.keysin@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.keysin/-/lodash.keysin-4.2.0.tgz#8cc3fb35c2d94acc443a1863e02fa40799ea6f28" - -lodash.mapvalues@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c" - -lodash.mergewith@^4.0.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" - -lodash.rest@^4.0.0: - version "4.0.5" - resolved "https://registry.yarnpkg.com/lodash.rest/-/lodash.rest-4.0.5.tgz#954ef75049262038c96d1fc98b28fdaf9f0772aa" - -lodash.restparam@^3.0.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - -lodash.some@^4.2.2: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" - -lodash.template@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-2.4.1.tgz#9e611007edf629129a974ab3c48b817b3e1cf20d" - dependencies: - lodash._escapestringchar "~2.4.1" - lodash._reinterpolate "~2.4.1" - lodash.defaults "~2.4.1" - lodash.escape "~2.4.1" - lodash.keys "~2.4.1" - lodash.templatesettings "~2.4.1" - lodash.values "~2.4.1" - -lodash.template@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" - dependencies: - lodash._basecopy "^3.0.0" - lodash._basetostring "^3.0.0" - lodash._basevalues "^3.0.0" - lodash._isiterateecall "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.keys "^3.0.0" - lodash.restparam "^3.0.0" - lodash.templatesettings "^3.0.0" - -lodash.templatesettings@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - -lodash.templatesettings@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz#ea76c75d11eb86d4dbe89a83893bb861929ac699" - dependencies: - lodash._reinterpolate "~2.4.1" - lodash.escape "~2.4.1" - -lodash.values@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-2.4.1.tgz#abf514436b3cb705001627978cbcf30b1280eea4" - dependencies: - lodash.keys "~2.4.1" - -lodash@4.16.2: - version "4.16.2" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.2.tgz#3e626db827048a699281a8a125226326cfc0e652" - -lodash@^3.3.1, lodash@^3.8.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" - -lodash@^4.0.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.16.2, lodash@^4.16.6, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.6.1, lodash@^4.8.0: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - -lodash@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" - -lodash@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.2.0.tgz#4bf50a3243f9aeb0bac41a55d3d5990675a462fb" - -log-driver@1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056" - -log4js@^0.6.31: - version "0.6.38" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-0.6.38.tgz#2c494116695d6fb25480943d3fc872e662a522fd" - dependencies: - readable-stream "~1.0.2" - semver "~4.3.3" - -lolex@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" - -longest-streak@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.1.tgz#42d291b5411e40365c00e63193497e2247316e35" - -longest@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" - -loose-envify@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" - dependencies: - js-tokens "^3.0.0" - -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" - -lru-cache@2: - version "2.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" - -lru-cache@2.2.x: - version "2.2.4" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d" - -lru-cache@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-cache@~2.6.5: - version "2.6.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5" - -lru-queue@0.1: - version "0.1.0" - resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" - dependencies: - es5-ext "~0.10.2" - -magic-string@^0.16.0: - version "0.16.0" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.16.0.tgz#970ebb0da7193301285fb1aa650f39bdd81eb45a" - dependencies: - vlq "^0.2.1" - -make-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978" - dependencies: - pify "^2.3.0" - -make-error-cause@^1.1.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d" - dependencies: - make-error "^1.2.0" - -make-error@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.0.tgz#52ad3a339ccf10ce62b4040b708fe707244b8b96" - -map-cache@^0.2.0, map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - -map-obj@^1.0.0, map-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - -map-stream@~0.0.3: - version "0.0.7" - resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" - -map-stream@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" - -map-visit@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-0.1.5.tgz#dbe43927ce5525b80dfc1573a44d68c51f26816b" - dependencies: - lazy-cache "^2.0.1" - object-visit "^0.3.4" - -markdown-escapes@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.1.tgz#1994df2d3af4811de59a6714934c2b2292734518" - -markdown-table@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.1.tgz#4b3dd3a133d1518b8ef0dbc709bf2a1b4824bc8c" - -"match-stream@>= 0.0.2 < 1": - version "0.0.2" - resolved "https://registry.yarnpkg.com/match-stream/-/match-stream-0.0.2.tgz#99eb050093b34dffade421b9ac0b410a9cfa17cf" - dependencies: - buffers "~0.1.1" - readable-stream "~1.0.0" - -md5.js@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -mdast-util-compact@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.1.tgz#cdb5f84e2b6a2d3114df33bd05d9cb32e3c4083a" - dependencies: - unist-util-modify-children "^1.0.0" - unist-util-visit "^1.1.0" - -mdast-util-definitions@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-1.2.2.tgz#673f4377c3e23d3de7af7a4fe2214c0e221c5ac7" - dependencies: - unist-util-visit "^1.0.0" - -mdast-util-inject@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz#db06b8b585be959a2dcd2f87f472ba9b756f3675" - dependencies: - mdast-util-to-string "^1.0.0" - -mdast-util-to-hast@^2.1.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-2.4.2.tgz#f116e8bf3da772ba5a397a92dab090f5ba91caa0" - dependencies: - collapse-white-space "^1.0.0" - detab "^2.0.0" - mdast-util-definitions "^1.2.0" - normalize-uri "^1.0.0" - trim "0.0.1" - trim-lines "^1.0.0" - unist-builder "^1.0.1" - unist-util-generated "^1.1.0" - unist-util-position "^3.0.0" - unist-util-visit "^1.1.0" - xtend "^4.0.1" - -mdast-util-to-string@^1.0.0, mdast-util-to-string@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.0.4.tgz#5c455c878c9355f0c1e7f3e8b719cf583691acfb" - -mdast-util-toc@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-toc/-/mdast-util-toc-2.0.1.tgz#b1d2cb23bfb01f812fa7b55bffe8b0a8bedf6f21" - dependencies: - github-slugger "^1.1.1" - mdast-util-to-string "^1.0.2" - unist-util-visit "^1.1.0" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - -mem@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" - dependencies: - mimic-fn "^1.0.0" - -memoizee@^0.4.5: - version "0.4.9" - resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.9.tgz#ea1c005f5c4c31d89a4a10e24db83fbf61cdd4f3" - dependencies: - d "1" - es5-ext "^0.10.30" - es6-weak-map "^2.0.2" - event-emitter "^0.3.5" - is-promise "^2.1" - lru-queue "0.1" - next-tick "1" - timers-ext "^0.1.2" - -memory-fs@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" - -memory-fs@^0.3.0, memory-fs@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.3.0.tgz#7bcc6b629e3a43e871d7e29aca6ae8a7f15cbb20" - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memory-fs@^0.4.0, memory-fs@~0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -meow@^3.3.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - -merge-descriptors@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - -merge-stream@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" - dependencies: - readable-stream "^2.0.1" - -method-override@~2.3.5: - version "2.3.9" - resolved "https://registry.yarnpkg.com/method-override/-/method-override-2.3.9.tgz#bd151f2ce34cf01a76ca400ab95c012b102d8f71" - dependencies: - debug "2.6.8" - methods "~1.1.2" - parseurl "~1.3.1" - vary "~1.1.1" - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - -micromatch@^2.1.5, micromatch@^2.3.7: - version "2.3.11" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" - dependencies: - arr-diff "^2.0.0" - array-unique "^0.2.1" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" - -micromatch@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.0.4.tgz#1543f1d04813447ac852001c5f5a933401786d1d" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.2.2" - define-property "^1.0.0" - extend-shallow "^2.0.1" - extglob "^1.1.0" - fragment-cache "^0.2.1" - kind-of "^4.0.0" - nanomatch "^1.2.0" - object.pick "^1.2.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -miller-rabin@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.0.tgz#4a62fb1d42933c05583982f4c716f6fb9e6c6d3d" - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -"mime-db@>= 1.29.0 < 2", mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" - -mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.6, mime-types@~2.1.7, mime-types@~2.1.9: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" - dependencies: - mime-db "~1.30.0" - -mime-types@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-1.0.2.tgz#995ae1392ab8affcbfcb2641dd054e943c0d5dce" - -mime@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" - -mime@^1.3.4: - version "1.4.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.0.tgz#69e9e0db51d44f2a3b56e48b7817d7d137f1a343" - -mime@~1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10" - -mimic-fn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" - -minimalistic-assert@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" - -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - dependencies: - brace-expansion "^1.1.7" - -minimatch@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" - dependencies: - brace-expansion "^1.0.0" - -minimatch@^2.0.1: - version "2.0.10" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" - dependencies: - brace-expansion "^1.0.0" - -minimatch@~0.2.11: - version "0.2.14" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" - dependencies: - lru-cache "2" - sigmund "~1.0.0" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - -minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - -minimist@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce" - -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - -mixin-deep@^1.1.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.2.0.tgz#d02b8c6f8b6d4b8f5982d3fd009c4919851c3fe2" - dependencies: - for-in "^1.0.2" - is-extendable "^0.1.1" - -mkdirp@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" - -mkdirp@0.3.x, mkdirp@~0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" - -mkdirp@0.5, mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - dependencies: - minimist "0.0.8" - -mkdirp@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" - dependencies: - minimist "0.0.8" - -mkpath@1.0.0, mkpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-1.0.0.tgz#ebb3a977e7af1c683ae6fda12b545a6ba6c5853d" - -mocha-nightwatch@3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/mocha-nightwatch/-/mocha-nightwatch-3.2.2.tgz#91bcb9b3bde057dd7677c78125e491e58d66647c" - dependencies: - browser-stdout "1.3.0" - commander "2.9.0" - debug "2.2.0" - diff "1.4.0" - escape-string-regexp "1.0.5" - glob "7.0.5" - growl "1.9.2" - json3 "3.3.2" - lodash.create "3.1.1" - mkdirp "0.5.1" - supports-color "3.1.2" - -mocha@^1.21.4: - version "1.21.5" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-1.21.5.tgz#7c58b09174df976e434a23b1e8d639873fc529e9" - dependencies: - commander "2.3.0" - debug "2.0.0" - diff "1.0.8" - escape-string-regexp "1.0.2" - glob "3.2.3" - growl "1.8.1" - jade "0.26.3" - mkdirp "0.5.0" - -mock-fs@^3.11.0: - version "3.12.1" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-3.12.1.tgz#ff27824cd6ab263a7eb05a115239d41d3631f5f8" - dependencies: - rewire "2.5.2" - semver "5.3.0" - -module-deps-sortable@4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/module-deps-sortable/-/module-deps-sortable-4.0.6.tgz#1251a4ba2c44a92df6989bd029da121a4f2109b0" - dependencies: - JSONStream "^1.0.3" - browser-resolve "^1.7.0" - concat-stream "~1.5.0" - defined "^1.0.0" - detective "^4.0.0" - duplexer2 "^0.1.2" - inherits "^2.0.1" - parents "^1.0.0" - readable-stream "^2.0.2" - resolve "^1.1.3" - stream-combiner2 "^1.1.1" - subarg "^1.0.0" - through2 "^2.0.0" - xtend "^4.0.0" - -module-not-found-error@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" - -morgan@~1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.6.1.tgz#5fd818398c6819cba28a7cd6664f292fe1c0bbf2" - dependencies: - basic-auth "~1.0.3" - debug "~2.2.0" - depd "~1.0.1" - on-finished "~2.3.0" - on-headers "~1.0.0" - -ms@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.6.2.tgz#d89c2124c6fdc1353d65a8b77bf1aac4b193708c" - -ms@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" - -ms@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - -multiparty@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/multiparty/-/multiparty-3.3.2.tgz#35de6804dc19643e5249f3d3e3bdc6c8ce301d3f" - dependencies: - readable-stream "~1.1.9" - stream-counter "~0.2.0" - -multipipe@^0.1.0, multipipe@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" - dependencies: - duplexer2 "0.0.2" - -mute-stream@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.4.tgz#a9219960a6d5d5d046597aee51252c6655f7177e" - -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - -nan@^2.3.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" - -nanomatch@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.0.tgz#76fdb3d4ae7617e37719e7a4047b840857c0cb1c" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^1.0.0" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - is-extglob "^2.1.1" - is-odd "^1.0.0" - kind-of "^4.0.0" - object.pick "^1.2.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -natives@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.0.tgz#e9ff841418a6b2ec7a495e939984f78f163e6e31" - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - -ncp@~0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/ncp/-/ncp-0.4.2.tgz#abcc6cbd3ec2ed2a729ff6e7c1fa8f01784a8574" - -negotiator@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.5.3.tgz#269d5c476810ec92edbe7b6c2f28316384f9a7e8" - -negotiator@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" - -netmask@~1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" - -next-tick@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - -nightwatch@^0.9.5: - version "0.9.16" - resolved "https://registry.yarnpkg.com/nightwatch/-/nightwatch-0.9.16.tgz#c4ac3ec711b0ff047c3dca9c6557365ee236519f" - dependencies: - chai-nightwatch "~0.1.x" - ejs "0.8.3" - lodash.clone "3.0.3" - lodash.defaultsdeep "4.3.2" - minimatch "3.0.3" - mkpath "1.0.0" - mocha-nightwatch "3.2.2" - optimist "0.6.1" - proxy-agent "2.0.0" - q "1.4.1" - -node-int64@~0.3.0: - version "0.3.3" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.3.3.tgz#2d6e6b2ece5de8588b43d88d1bc41b26cd1fa84d" - -node-libs-browser@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-0.7.0.tgz#3e272c0819e308935e26674408d7af0e1491b83b" - dependencies: - assert "^1.1.1" - browserify-zlib "^0.1.4" - buffer "^4.9.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "3.3.0" - domain-browser "^1.1.1" - events "^1.0.0" - https-browserify "0.0.1" - os-browserify "^0.2.0" - path-browserify "0.0.0" - process "^0.11.0" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.0.5" - stream-browserify "^2.0.1" - stream-http "^2.3.1" - string_decoder "^0.10.25" - timers-browserify "^2.0.2" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.10.3" - vm-browserify "0.0.4" - -node-libs-browser@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.0.0.tgz#a3a59ec97024985b46e958379646f96c4b616646" - dependencies: - assert "^1.1.1" - browserify-zlib "^0.1.4" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^1.0.0" - https-browserify "0.0.1" - os-browserify "^0.2.0" - path-browserify "0.0.0" - process "^0.11.0" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.0.5" - stream-browserify "^2.0.1" - stream-http "^2.3.1" - string_decoder "^0.10.25" - timers-browserify "^2.0.2" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.10.3" - vm-browserify "0.0.4" - -node-pre-gyp@^0.6.36: - version "0.6.36" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" - dependencies: - mkdirp "^0.5.1" - nopt "^4.0.1" - npmlog "^4.0.2" - rc "^1.1.7" - request "^2.81.0" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^2.2.1" - tar-pack "^3.4.0" - -node-uuid@~1.4.0: - version "1.4.8" - resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" - -nopt@3.x: - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - dependencies: - abbrev "1" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: - version "2.4.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" - dependencies: - hosted-git-info "^2.1.4" - is-builtin-module "^1.0.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-uri@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/normalize-uri/-/normalize-uri-1.1.0.tgz#01fb440c7fd059b9d9be8645aac14341efd059dd" - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - dependencies: - path-key "^2.0.0" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -null-check@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - -oauth-sign@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.5.0.tgz#d767f5169325620eab2e087ef0c472e773db6461" - -oauth-sign@~0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - -object-assign@*, object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - -object-assign@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" - -object-assign@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" - -object-component@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-keys@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" - -object-visit@^0.3.4: - version "0.3.4" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-0.3.4.tgz#ae15cf86f0b2fdd551771636448452c54c3da829" - dependencies: - isobject "^2.0.0" - -object.defaults@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" - dependencies: - array-each "^1.0.1" - array-slice "^1.0.0" - for-own "^1.0.0" - isobject "^3.0.0" - -object.omit@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" - dependencies: - for-own "^0.1.4" - is-extendable "^0.1.1" - -object.pick@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - dependencies: - isobject "^3.0.1" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.0, on-headers@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" - -once@1.x, once@^1.3.0, once@^1.3.3, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - dependencies: - wrappy "1" - -once@~1.3.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" - dependencies: - wrappy "1" - -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - dependencies: - mimic-fn "^1.0.0" - -open@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/open/-/open-0.0.5.tgz#42c3e18ec95466b6bf0dc42f3a2945c3f0cad8fc" - -openurl@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/openurl/-/openurl-1.1.1.tgz#3875b4b0ef7a52c156f0db41d4609dbb0f94b387" - -optimist@0.6.1, optimist@^0.6.1, optimist@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - -optimize-js@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/optimize-js/-/optimize-js-1.0.3.tgz#4326af8657c4a5ff32daf726631754f72ab7fdbc" - dependencies: - acorn "^3.3.0" - concat-stream "^1.5.1" - estree-walker "^0.3.0" - magic-string "^0.16.0" - yargs "^4.8.1" - -optionator@^0.8.1, optionator@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.4" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - wordwrap "~1.0.0" - -options@>=0.0.5: - version "0.0.6" - resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" - -orchestrator@^0.3.0: - version "0.3.8" - resolved "https://registry.yarnpkg.com/orchestrator/-/orchestrator-0.3.8.tgz#14e7e9e2764f7315fbac184e506c7aa6df94ad7e" - dependencies: - end-of-stream "~0.1.5" - sequencify "~0.0.7" - stream-consume "~0.1.0" - -ordered-read-streams@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126" - -ordered-read-streams@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b" - dependencies: - is-stream "^1.0.1" - readable-stream "^2.0.1" - -os-browserify@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f" - -os-homedir@^1.0.0, os-homedir@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - dependencies: - lcid "^1.0.0" - -os-locale@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" - dependencies: - execa "^0.7.0" - lcid "^1.0.0" - mem "^1.1.0" - -os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - -osenv@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -"over@>= 0.0.5 < 1": - version "0.0.5" - resolved "https://registry.yarnpkg.com/over/-/over-0.0.5.tgz#f29852e70fd7e25f360e013a8ec44c82aedb5708" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - -p-limit@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" - -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - dependencies: - p-limit "^1.1.0" - -pac-proxy-agent@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz#34a385dfdf61d2f0ecace08858c745d3e791fd4d" - dependencies: - agent-base "2" - debug "2" - extend "3" - get-uri "2" - http-proxy-agent "1" - https-proxy-agent "1" - pac-resolver "~2.0.0" - raw-body "2" - socks-proxy-agent "2" - -pac-resolver@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-2.0.0.tgz#99b88d2f193fbdeefc1c9a529c1f3260ab5277cd" - dependencies: - co "~3.0.6" - degenerator "~1.0.2" - ip "1.0.1" - netmask "~1.0.4" - thunkify "~2.1.1" - -pako@~0.2.0: - version "0.2.9" - resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" - -parents@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751" - dependencies: - path-platform "~0.11.15" - -parse-asn1@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" - dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - -parse-entities@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.1.1.tgz#8112d88471319f27abae4d64964b122fe4e1b890" - dependencies: - character-entities "^1.0.0" - character-entities-legacy "^1.0.0" - character-reference-invalid "^1.0.0" - is-alphanumerical "^1.0.0" - is-decimal "^1.0.0" - is-hexadecimal "^1.0.0" - -parse-filepath@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.1.tgz#159d6155d43904d16c10ef698911da1e91969b73" - dependencies: - is-absolute "^0.2.3" - map-cache "^0.2.0" - path-root "^0.1.1" - -parse-git-config@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/parse-git-config/-/parse-git-config-0.2.0.tgz#272833fdd15fea146fb75d336d236b963b6ff706" - dependencies: - ini "^1.3.3" - -parse-glob@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" - dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" - -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - dependencies: - error-ex "^1.2.0" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - -parse-url@^1.3.0: - version "1.3.11" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-1.3.11.tgz#57c15428ab8a892b1f43869645c711d0e144b554" - dependencies: - is-ssh "^1.3.0" - protocols "^1.4.0" - -parsejson@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab" - dependencies: - better-assert "~1.0.0" - -parseqs@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" - dependencies: - better-assert "~1.0.0" - -parseuri@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" - dependencies: - better-assert "~1.0.0" - -parseurl@~1.3.0, parseurl@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - -path-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - dependencies: - pinkie-promise "^2.0.0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - -path-is-inside@^1.0.1, path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - -path-key@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - -path-parse@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" - -path-platform@~0.11.15: - version "0.11.15" - resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2" - -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" - -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" - dependencies: - path-root-regex "^0.1.0" - -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -path-type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" - dependencies: - pify "^2.0.0" - -pause-stream@0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" - dependencies: - through "~2.3" - -pause@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/pause/-/pause-0.1.0.tgz#ebc8a4a8619ff0b8a81ac1513c3434ff469fdb74" - -pbkdf2-compat@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz#b6e0c8fa99494d94e0511575802a59a5c142f288" - -pbkdf2@^3.0.3: - version "3.0.13" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.13.tgz#c37d295531e786b1da3e3eadc840426accb0ae25" - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -performance-now@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" - -pify@^2.0.0, pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - -pkg-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" - dependencies: - find-up "^1.0.0" - -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - dependencies: - find-up "^2.1.0" - -pluralize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-4.0.0.tgz#59b708c1c0190a2f692f1c7618c446b052fd1762" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - -pretty-hrtime@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" - -private@^0.1.6, private@^0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" - -process-nextick-args@^1.0.6, process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - -process@^0.11.0: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - -progress@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" - -property-information@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-3.2.0.tgz#fd1483c8fbac61808f5fe359e7693a1f48a58331" - -protocols@^1.1.0, protocols@^1.4.0: - version "1.4.5" - resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.5.tgz#21de1f441c4ef7094408ed9f1c94f7a114b87557" - -proxy-agent@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-2.0.0.tgz#57eb5347aa805d74ec681cb25649dba39c933499" - dependencies: - agent-base "2" - debug "2" - extend "3" - http-proxy-agent "1" - https-proxy-agent "1" - lru-cache "~2.6.5" - pac-proxy-agent "1" - socks-proxy-agent "2" - -proxyquire@^1.7.10: - version "1.8.0" - resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-1.8.0.tgz#02d514a5bed986f04cbb2093af16741535f79edc" - dependencies: - fill-keys "^1.0.2" - module-not-found-error "^1.0.0" - resolve "~1.1.7" - -prr@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" - -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - -public-encrypt@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - -"pullstream@>= 0.4.1 < 1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/pullstream/-/pullstream-0.4.1.tgz#d6fb3bf5aed697e831150eb1002c25a3f8ae1314" - dependencies: - over ">= 0.0.5 < 1" - readable-stream "~1.0.31" - setimmediate ">= 1.0.2 < 2" - slice-stream ">= 1.0.0 < 2" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - -punycode@^1.2.4, punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - -q@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" - -q@^1.5.0, q@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" - -q@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/q/-/q-1.3.0.tgz#850d79f8cb831d92e103b46483e4e35d34640050" - -qjobs@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.1.5.tgz#659de9f2cf8dcc27a1481276f205377272382e73" - -qs@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-4.0.0.tgz#c31d9b74ec27df75e543a86c78728ed8d4623607" - -qs@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.0.tgz#a9f31142af468cb72b25b30136ba2456834916be" - -qs@6.4.0, qs@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" - -qs@^6.4.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49" - -qs@~2.3.1: - version "2.3.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-2.3.3.tgz#e9e85adbe75da0bbe4c8e0476a086290f863b404" - -qs@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-5.1.0.tgz#4d932e5c7ea411cca76a312d39a606200fd50cd9" - -qs@~6.3.0: - version "6.3.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - -querystringify@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.3.tgz#0c9d36fbf8c7a4f71eb370857763577a63335be7" - -querystringify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" - -random-bytes@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" - -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -randombytes@^2.0.0, randombytes@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.5.tgz#dc009a246b8d09a177b4b7a0ae77bc570f4b1b79" - dependencies: - safe-buffer "^5.1.0" - -range-parser@^1.0.3, range-parser@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - -range-parser@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.0.3.tgz#6872823535c692e2c2a0103826afd82c2e0ff175" - -raw-body@2: - version "2.3.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.0.tgz#f79ce1acacaba5b6362d33454d785d7129f4bc67" - dependencies: - bytes "2.5.0" - http-errors "1.6.1" - iconv-lite "0.4.18" - unpipe "1.0.0" - -raw-body@~1.1.0: - version "1.1.7" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" - dependencies: - bytes "1" - string_decoder "0.10" - -raw-body@~2.1.2, raw-body@~2.1.5: - version "2.1.7" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.1.7.tgz#adfeace2e4fb3098058014d08c072dcc59758774" - dependencies: - bytes "2.4.0" - iconv-lite "0.4.13" - unpipe "1.0.0" - -raw-body@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96" - dependencies: - bytes "2.4.0" - iconv-lite "0.4.15" - unpipe "1.0.0" - -rc@^1.1.7: - version "1.2.1" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" - dependencies: - deep-extend "~0.4.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -read-pkg-up@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" - -read-pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" - dependencies: - find-up "^2.0.0" - read-pkg "^2.0.0" - -read-pkg@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" - -read-pkg@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" - dependencies: - load-json-file "^2.0.0" - normalize-package-data "^2.3.2" - path-type "^2.0.0" - -readable-stream@1.1.x, readable-stream@~1.1.8, readable-stream@~1.1.9: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.6: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - -"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.0, readable-stream@~1.0.17, readable-stream@~1.0.2, readable-stream@~1.0.24, readable-stream@~1.0.26, readable-stream@~1.0.31, readable-stream@~1.0.33: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@~2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - -readable-stream@~2.1.0: - version "2.1.5" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" - dependencies: - buffer-shims "^1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - -readdirp@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" - dependencies: - graceful-fs "^4.1.2" - minimatch "^3.0.2" - readable-stream "^2.0.2" - set-immediate-shim "^1.0.1" - -readline2@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/readline2/-/readline2-0.1.1.tgz#99443ba6e83b830ef3051bfd7dc241a82728d568" - dependencies: - mute-stream "0.0.4" - strip-ansi "^2.0.1" - -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - dependencies: - resolve "^1.1.6" - -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - -regenerate@^1.2.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" - -regenerator-runtime@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" - -regenerator-transform@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" - dependencies: - babel-runtime "^6.18.0" - babel-types "^6.19.0" - private "^0.1.6" - -regex-cache@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" - dependencies: - is-equal-shallow "^0.1.3" - -regex-not@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-0.1.2.tgz#bc7f1c4944b1188353d07deeb912b94e0ade25db" - -regex-not@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.0.tgz#42f83e39771622df826b02af176525d6a5f157f9" - dependencies: - extend-shallow "^2.0.1" - -regexpu-core@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" - -regjsgen@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" - -regjsparser@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" - dependencies: - jsesc "~0.5.0" - -remark-html@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/remark-html/-/remark-html-6.0.1.tgz#5094d2c71f7941fdb2ae865bac76627757ce09c1" - dependencies: - hast-util-sanitize "^1.0.0" - hast-util-to-html "^3.0.0" - mdast-util-to-hast "^2.1.1" - xtend "^4.0.1" - -remark-parse@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-4.0.0.tgz#99f1f049afac80382366e2e0d0bd55429dd45d8b" - dependencies: - collapse-white-space "^1.0.2" - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - is-word-character "^1.0.0" - markdown-escapes "^1.0.0" - parse-entities "^1.0.2" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - trim "0.0.1" - trim-trailing-lines "^1.0.0" - unherit "^1.0.4" - unist-util-remove-position "^1.0.0" - vfile-location "^2.0.0" - xtend "^4.0.1" - -remark-slug@^4.0.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/remark-slug/-/remark-slug-4.2.3.tgz#8d987d0e5e63d4a49ea37b90fe999a3dcfc81b72" - dependencies: - github-slugger "^1.0.0" - mdast-util-to-string "^1.0.0" - unist-util-visit "^1.0.0" - -remark-stringify@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-4.0.0.tgz#4431884c0418f112da44991b4e356cfe37facd87" - dependencies: - ccount "^1.0.0" - is-alphanumeric "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - longest-streak "^2.0.1" - markdown-escapes "^1.0.0" - markdown-table "^1.1.0" - mdast-util-compact "^1.0.0" - parse-entities "^1.0.2" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - stringify-entities "^1.0.1" - unherit "^1.0.4" - xtend "^4.0.1" - -remark-toc@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/remark-toc/-/remark-toc-4.0.1.tgz#ff36ff6de54ea07dd59e3f5334a4a3aac1e93185" - dependencies: - mdast-util-toc "^2.0.0" - remark-slug "^4.0.0" - -remark@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/remark/-/remark-8.0.0.tgz#287b6df2fe1190e263c1d15e486d3fa835594d6d" - dependencies: - remark-parse "^4.0.0" - remark-stringify "^4.0.0" - unified "^6.0.0" - -remote-origin-url@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/remote-origin-url/-/remote-origin-url-0.4.0.tgz#4d3e2902f34e2d37d1c263d87710b77eb4086a30" - dependencies: - parse-git-config "^0.2.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - -repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - -repeat-string@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-0.2.2.tgz#c7a8d3236068362059a7e4651fc6884e8b1fb4ae" - -repeat-string@^1.5.0, repeat-string@^1.5.2, repeat-string@^1.5.4, repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - dependencies: - is-finite "^1.0.0" - -replace-ext@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" - -replace-ext@1.0.0, replace-ext@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" - -replacestream@0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/replacestream/-/replacestream-0.1.3.tgz#e018d3a37724600ccd0c005990d8a21b7b54ff34" - dependencies: - through "~2.3.4" - -request@2.49.0: - version "2.49.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.49.0.tgz#0d4f6348dc3348059b553e4db60fd2478de662a7" - dependencies: - aws-sign2 "~0.5.0" - bl "~0.9.0" - caseless "~0.8.0" - combined-stream "~0.0.5" - forever-agent "~0.5.0" - form-data "~0.1.0" - hawk "1.1.1" - http-signature "~0.10.0" - json-stringify-safe "~5.0.0" - mime-types "~1.0.1" - node-uuid "~1.4.0" - oauth-sign "~0.5.0" - qs "~2.3.1" - stringstream "~0.0.4" - tough-cookie ">=0.12.0" - tunnel-agent "~0.4.0" - -request@2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~2.0.6" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - qs "~6.3.0" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" - uuid "^3.0.0" - -request@2.81.0, request@^2.81.0: - version "2.81.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~4.2.1" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - performance-now "^0.2.0" - qs "~6.4.0" - safe-buffer "^5.0.1" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "^0.6.0" - uuid "^3.0.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - -require-uncached@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" - dependencies: - caller-path "^0.1.0" - resolve-from "^1.0.0" - -requirejs@^2.1.20: - version "2.3.5" - resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.5.tgz#617b9acbbcb336540ef4914d790323a8d4b861b0" - -requires-port@1.0.x, requires-port@1.x.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - -resolve-dir@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" - dependencies: - expand-tilde "^1.2.2" - global-modules "^0.2.3" - -resolve-from@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" - -resolve-url@^0.2.1, resolve-url@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - -resolve@1.1.7, resolve@1.1.x, resolve@~1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - -resolve@^1.1.3, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.2.0, resolve@^1.3.3: - version "1.4.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" - dependencies: - path-parse "^1.0.5" - -response-time@~2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/response-time/-/response-time-2.3.2.tgz#ffa71bab952d62f7c1d49b7434355fbc68dffc5a" - dependencies: - depd "~1.1.0" - on-headers "~1.0.1" - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - -rewire@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/rewire/-/rewire-2.5.2.tgz#6427de7b7feefa7d36401507eb64a5385bc58dc7" - -rgb2hex@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/rgb2hex/-/rgb2hex-0.1.0.tgz#ccd55f860ae0c5c4ea37504b958e442d8d12325b" - -right-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" - dependencies: - align-text "^0.1.1" - -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" - dependencies: - glob "^7.0.5" - -rimraf@~2.2.0: - version "2.2.8" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" - -ripemd160@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-0.2.0.tgz#2bf198bde167cacfa51c0a928e84b68bbe171fce" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" - dependencies: - hash-base "^2.0.0" - inherits "^2.0.1" - -rndm@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/rndm/-/rndm-1.2.0.tgz#f33fe9cfb52bbfd520aa18323bc65db110a1b76c" - -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - dependencies: - is-promise "^2.1.0" - -rx-lite-aggregates@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" - dependencies: - rx-lite "*" - -rx-lite@*, rx-lite@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" - -rx@^2.4.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/rx/-/rx-2.5.3.tgz#21adc7d80f02002af50dae97fd9dbf248755f566" - -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - -safe-json-parse@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" - -samsam@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" - -samsam@~1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" - -sauce-connect-launcher@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/sauce-connect-launcher/-/sauce-connect-launcher-1.2.2.tgz#7346cc8fbdc443191323439b0733451f5f3521f2" - dependencies: - adm-zip "~0.4.3" - async "^2.1.2" - https-proxy-agent "~1.0.0" - lodash "^4.16.6" - rimraf "^2.5.4" - -saucelabs@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/saucelabs/-/saucelabs-1.4.0.tgz#b934a9af9da2874b3f40aae1fcde50a4466f5f38" - dependencies: - https-proxy-agent "^1.0.0" - -schema-utils@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" - dependencies: - ajv "^5.0.0" - -"semver@2 || 3 || 4 || 5", semver@^5.3.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" - -semver@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" - -semver@^4.1.0, semver@~4.3.3: - version "4.3.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" - -semver@~5.0.1: - version "5.0.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" - -send@0.13.2: - version "0.13.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.13.2.tgz#765e7607c8055452bba6f0b052595350986036de" - dependencies: - debug "~2.2.0" - depd "~1.1.0" - destroy "~1.0.4" - escape-html "~1.0.3" - etag "~1.7.0" - fresh "0.3.0" - http-errors "~1.3.1" - mime "1.3.4" - ms "0.7.1" - on-finished "~2.3.0" - range-parser "~1.0.3" - statuses "~1.2.1" - -sequencify@~0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/sequencify/-/sequencify-0.0.7.tgz#90cff19d02e07027fd767f5ead3e7b95d1e7380c" - -serve-favicon@~2.3.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.3.2.tgz#dd419e268de012ab72b319d337f2105013f9381f" - dependencies: - etag "~1.7.0" - fresh "0.3.0" - ms "0.7.2" - parseurl "~1.3.1" - -serve-index@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.7.3.tgz#7a057fc6ee28dc63f64566e5fa57b111a86aecd2" - dependencies: - accepts "~1.2.13" - batch "0.5.3" - debug "~2.2.0" - escape-html "~1.0.3" - http-errors "~1.3.1" - mime-types "~2.1.9" - parseurl "~1.3.1" - -serve-static@~1.10.0: - version "1.10.3" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.10.3.tgz#ce5a6ecd3101fed5ec09827dac22a9c29bfb0535" - dependencies: - escape-html "~1.0.3" - parseurl "~1.3.1" - send "0.13.2" - -set-blocking@^2.0.0, set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - -set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - dependencies: - to-object-path "^0.3.0" - -set-immediate-shim@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" - -set-value@^0.4.2, set-value@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.1" - to-object-path "^0.3.0" - -"setimmediate@>= 1.0.1 < 2", "setimmediate@>= 1.0.2 < 2", setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - -setprototypeof@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" - -sha.js@2.2.6: - version "2.2.6" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba" - -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.8" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f" - dependencies: - inherits "^2.0.1" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - -shelljs@^0.7.5: - version "0.7.8" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - -sigmund@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - -sinon@^1.12.1: - version "1.17.7" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" - dependencies: - formatio "1.1.1" - lolex "1.3.2" - samsam "1.1.2" - util ">=0.10.3 <1" - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - -slice-ansi@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" - -"slice-stream@>= 1.0.0 < 2": - version "1.0.0" - resolved "https://registry.yarnpkg.com/slice-stream/-/slice-stream-1.0.0.tgz#5b33bd66f013b1a7f86460b03d463dec39ad3ea0" - dependencies: - readable-stream "~1.0.31" - -smart-buffer@^1.0.13: - version "1.1.15" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16" - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370" - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^2.0.0" - -sntp@0.2.x: - version "0.2.4" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-0.2.4.tgz#fb885f18b0f3aad189f824862536bceeec750900" - dependencies: - hoek "0.9.x" - -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" - dependencies: - hoek "2.x.x" - -socket.io-adapter@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz#cb6d4bb8bec81e1078b99677f9ced0046066bb8b" - dependencies: - debug "2.3.3" - socket.io-parser "2.3.1" - -socket.io-client@1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.3.tgz#b30e86aa10d5ef3546601c09cde4765e381da377" - dependencies: - backo2 "1.0.2" - component-bind "1.0.0" - component-emitter "1.2.1" - debug "2.3.3" - engine.io-client "1.8.3" - has-binary "0.1.7" - indexof "0.0.1" - object-component "0.0.3" - parseuri "0.0.5" - socket.io-parser "2.3.1" - to-array "0.1.4" - -socket.io-parser@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.3.1.tgz#dd532025103ce429697326befd64005fcfe5b4a0" - dependencies: - component-emitter "1.1.2" - debug "2.2.0" - isarray "0.0.1" - json3 "3.3.2" - -socket.io@1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.3.tgz#b8af9caba00949e568e369f1327ea9be9ea2461b" - dependencies: - debug "2.3.3" - engine.io "1.8.3" - has-binary "0.1.7" - object-assign "4.1.0" - socket.io-adapter "0.5.0" - socket.io-client "1.7.3" - socket.io-parser "2.3.1" - -socks-proxy-agent@2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-2.1.1.tgz#86ebb07193258637870e13b7bd99f26c663df3d3" - dependencies: - agent-base "2" - extend "3" - socks "~1.1.5" - -socks@~1.1.5: - version "1.1.10" - resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.10.tgz#5b8b7fc7c8f341c53ed056e929b7bf4de8ba7b5a" - dependencies: - ip "^1.1.4" - smart-buffer "^1.0.13" - -source-list-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" - -source-list-map@~0.1.7: - version "0.1.8" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" - -source-map-resolve@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.3.1.tgz#610f6122a445b8dd51535a2a71b783dfc1248761" - dependencies: - atob "~1.1.0" - resolve-url "~0.2.1" - source-map-url "~0.3.0" - urix "~0.1.0" - -source-map-resolve@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.0.tgz#fcad0b64b70afb27699e425950cb5ebcd410bc20" - dependencies: - atob "^2.0.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@^0.4.15: - version "0.4.17" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.17.tgz#6f2150553e6375375d0ccb3180502b78c18ba430" - dependencies: - source-map "^0.5.6" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - -source-map-url@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" - -source-map@0.X, source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - -source-map@^0.1.38, source-map@^0.1.41, source-map@~0.1.38: - version "0.1.43" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" - dependencies: - amdefine ">=0.0.4" - -source-map@^0.4.4, source-map@~0.4.1: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - dependencies: - amdefine ">=0.0.4" - -source-map@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" - dependencies: - amdefine ">=0.0.4" - -space-separated-tokens@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.1.tgz#9695b9df9e65aec1811d4c3f9ce52520bc2f7e4d" - dependencies: - trim "0.0.1" - -sparkles@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" - -spdx-correct@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" - dependencies: - spdx-license-ids "^1.0.2" - -spdx-expression-parse@~1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" - -spdx-license-ids@^1.0.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" - -split-string@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-2.1.1.tgz#af4b06d821560426446c3cd931cda618940d37d0" - dependencies: - extend-shallow "^2.0.1" - -split@0.2: - version "0.2.10" - resolved "https://registry.yarnpkg.com/split/-/split-0.2.10.tgz#67097c601d697ce1368f418f06cd201cf0521a57" - dependencies: - through "2" - -split@0.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" - dependencies: - through "2" - -sprintf-js@^1.0.3: - version "1.1.1" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - -sshpk@^1.7.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - optionalDependencies: - bcrypt-pbkdf "^1.0.0" - ecc-jsbn "~0.1.1" - jsbn "~0.1.0" - tweetnacl "~0.14.0" - -state-toggle@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.0.tgz#d20f9a616bb4f0c3b98b91922d25b640aa2bc425" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -statuses@1, "statuses@>= 1.3.1 < 2", statuses@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" - -statuses@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.2.1.tgz#dded45cc18256d51ed40aec142489d5c61026d28" - -stream-array@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/stream-array/-/stream-array-1.1.2.tgz#9e5f7345f2137c30ee3b498b9114e80b52bb7eb5" - dependencies: - readable-stream "~2.1.0" - -stream-browserify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-combiner2@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" - dependencies: - duplexer2 "~0.1.0" - readable-stream "^2.0.2" - -stream-combiner@~0.0.3, stream-combiner@~0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" - dependencies: - duplexer "~0.1.1" - -stream-consume@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f" - -stream-counter@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/stream-counter/-/stream-counter-0.2.0.tgz#ded266556319c8b0e222812b9cf3b26fa7d947de" - dependencies: - readable-stream "~1.1.8" - -stream-http@^2.3.1: - version "2.7.2" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad" - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.2.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - -string-replace-webpack-plugin@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/string-replace-webpack-plugin/-/string-replace-webpack-plugin-0.1.3.tgz#73c657e759d66cfe80ae1e0cf091aa256d0e715c" - dependencies: - async "~0.2.10" - loader-utils "~0.2.3" - optionalDependencies: - css-loader "^0.9.1" - file-loader "^0.8.1" - style-loader "^0.8.3" - -string-template@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" - -string-width@^1.0.0, string-width@^1.0.1, string-width@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -string-width@^2.0.0, string-width@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string_decoder@0.10, string_decoder@^0.10.25, string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - dependencies: - safe-buffer "~5.1.0" - -stringify-entities@^1.0.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-1.3.1.tgz#b150ec2d72ac4c1b5f324b51fb6b28c9cdff058c" - dependencies: - character-entities-html4 "^1.0.0" - character-entities-legacy "^1.0.0" - is-alphanumerical "^1.0.0" - is-hexadecimal "^1.0.0" - -stringstream@~0.0.4: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" - -strip-ansi@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" - dependencies: - ansi-regex "^0.2.1" - -strip-ansi@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-2.0.1.tgz#df62c1aa94ed2f114e1d0f21fd1d50482b79a60e" - dependencies: - ansi-regex "^1.0.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - dependencies: - ansi-regex "^3.0.0" - -strip-bom-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee" - dependencies: - first-chunk-stream "^1.0.0" - strip-bom "^2.0.0" - -strip-bom-string@1.X: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" - -strip-bom@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794" - dependencies: - first-chunk-stream "^1.0.0" - is-utf8 "^0.2.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - dependencies: - is-utf8 "^0.2.0" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - -strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - dependencies: - get-stdin "^4.0.1" - -strip-json-comments@^2.0.0, strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - -style-loader@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.8.3.tgz#f4f92eb7db63768748f15065cd6700f5a1c85357" - dependencies: - loader-utils "^0.2.5" - -subarg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" - dependencies: - minimist "^1.1.0" - -supports-color@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" - dependencies: - has-flag "^1.0.0" - -supports-color@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" - -supports-color@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.3.1.tgz#15758df09d8ff3b4acc307539fabe27095e1042d" - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - -supports-color@^3.1.0, supports-color@^3.1.2: - version "3.2.3" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" - dependencies: - has-flag "^1.0.0" - -supports-color@^4.0.0, supports-color@^4.1.0, supports-color@^4.2.1: - version "4.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" - dependencies: - has-flag "^2.0.0" - -table@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/table/-/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435" - dependencies: - ajv "^4.7.0" - ajv-keywords "^1.0.0" - chalk "^1.1.1" - lodash "^4.0.0" - slice-ansi "0.0.4" - string-width "^2.0.0" - -tapable@^0.1.8, tapable@~0.1.8: - version "0.1.10" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" - -tapable@^0.2.7: - version "0.2.8" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" - -tar-pack@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" - dependencies: - debug "^2.2.0" - fstream "^1.0.10" - fstream-ignore "^1.0.5" - once "^1.3.3" - readable-stream "^2.1.4" - rimraf "^2.5.1" - tar "^2.2.1" - uid-number "^0.0.6" - -tar-stream@^1.5.0: - version "1.5.4" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.4.tgz#36549cf04ed1aee9b2a30c0143252238daf94016" - dependencies: - bl "^1.0.0" - end-of-stream "^1.0.0" - readable-stream "^2.0.0" - xtend "^4.0.0" - -tar-stream@~1.1.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.1.5.tgz#be9218c130c20029e107b0f967fb23de0579d13c" - dependencies: - bl "^0.9.0" - end-of-stream "^1.0.0" - readable-stream "~1.0.33" - xtend "^4.0.0" - -tar@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" - dependencies: - block-stream "*" - fstream "^1.0.2" - inherits "2" - -ternary-stream@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ternary-stream/-/ternary-stream-2.0.1.tgz#064e489b4b5bf60ba6a6b7bc7f2f5c274ecf8269" - dependencies: - duplexify "^3.5.0" - fork-stream "^0.0.4" - merge-stream "^1.0.0" - through2 "^2.0.1" - -text-table@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - -textextensions@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-1.0.2.tgz#65486393ee1f2bb039a60cbba05b0b68bd9501d2" - -through2-filter@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" - dependencies: - through2 "~2.0.0" - xtend "~4.0.0" - -through2@2.X, through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" - dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - -through2@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.4.2.tgz#dbf5866031151ec8352bb6c4db64a2292a840b9b" - dependencies: - readable-stream "~1.0.17" - xtend "~2.1.1" - -through2@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.5.1.tgz#dfdd012eb9c700e2323fd334f38ac622ab372da7" - dependencies: - readable-stream "~1.0.17" - xtend "~3.0.0" - -through2@^0.6.0, through2@^0.6.1, through2@^0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" - dependencies: - readable-stream ">=1.0.33-1 <1.1.0-0" - xtend ">=4.0.0 <4.1.0-0" - -through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1, through@~2.3.4: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - -thunkify@~2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d" - -tildify@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a" - dependencies: - os-homedir "^1.0.0" - -time-stamp@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" - -time-stamp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357" - -timers-browserify@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.4.tgz#96ca53f4b794a5e7c0e1bd7cc88a372298fa01e6" - dependencies: - setimmediate "^1.0.4" - -timers-ext@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.2.tgz#61cc47a76c1abd3195f14527f978d58ae94c5204" - dependencies: - es5-ext "~0.10.14" - next-tick "1" - -tiny-lr@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-0.2.1.tgz#b3fdba802e5d56a33c2f6f10794b32e477ac729d" - dependencies: - body-parser "~1.14.0" - debug "~2.2.0" - faye-websocket "~0.10.0" - livereload-js "^2.2.0" - parseurl "~1.3.0" - qs "~5.1.0" - -tiny-lr@^1.0.3: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.0.5.tgz#21f40bf84ebd1f853056680375eef1670c334112" - dependencies: - body "^5.1.0" - debug "~2.6.7" - faye-websocket "~0.10.0" - livereload-js "^2.2.2" - object-assign "^4.1.0" - qs "^6.4.0" - -tmp@0.0.31, tmp@^0.0.31: - version "0.0.31" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" - dependencies: - os-tmpdir "~1.0.1" - -tmp@0.0.x: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - dependencies: - os-tmpdir "~1.0.2" - -to-absolute-glob@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f" - dependencies: - extend-shallow "^2.0.1" - -to-array@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-2.1.0.tgz#e3ad3a40cfe119559a05aea43e4caefacc5e901d" - dependencies: - define-property "^0.2.5" - extend-shallow "^2.0.1" - regex-not "^0.1.1" - -to-regex@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.1.tgz#15358bee4a2c83bd76377ba1dc049d0f18837aae" - dependencies: - define-property "^0.2.5" - extend-shallow "^2.0.1" - regex-not "^1.0.0" - -tough-cookie@>=0.12.0, tough-cookie@~2.3.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" - dependencies: - punycode "^1.4.1" - -"traverse@>=0.3.0 <0.4": - version "0.3.9" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" - -trim-lines@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-1.1.0.tgz#9926d03ede13ba18f7d42222631fb04c79ff26fe" - -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - -trim-trailing-lines@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.0.tgz#7aefbb7808df9d669f6da2e438cac8c46ada7684" - -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - -trough@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.1.tgz#a9fd8b0394b0ae8fff82e0633a0a36ccad5b5f86" - -tryit@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" - -tsscmp@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.5.tgz#7dc4a33af71581ab4337da91d85ca5427ebd9a97" - -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - dependencies: - safe-buffer "^5.0.1" - -tunnel-agent@~0.4.0, tunnel-agent@~0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - dependencies: - prelude-ls "~1.1.2" - -type-detect@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" - -type-detect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" - -type-is@~1.6.10, type-is@~1.6.15, type-is@~1.6.6: - version "1.6.15" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" - dependencies: - media-typer "0.3.0" - mime-types "~2.1.15" - -typedarray@^0.0.6, typedarray@~0.0.5: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - -uglify-js@^2.6, uglify-js@^2.8.10, uglify-js@^2.8.29: - version "2.8.29" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" - dependencies: - source-map "~0.5.1" - yargs "~3.10.0" - optionalDependencies: - uglify-to-browserify "~1.0.0" - -uglify-js@^3.0.5: - version "3.0.28" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.0.28.tgz#96b8495f0272944787b5843a1679aa326640d5f7" - dependencies: - commander "~2.11.0" - source-map "~0.5.1" - -uglify-js@~2.7.3: - version "2.7.5" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" - dependencies: - async "~0.2.6" - source-map "~0.5.1" - uglify-to-browserify "~1.0.0" - yargs "~3.10.0" - -uglify-to-browserify@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" - -uglifyjs-webpack-plugin@^0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" - dependencies: - source-map "^0.5.6" - uglify-js "^2.8.29" - webpack-sources "^1.0.1" - -uid-number@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" - -uid-safe@2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.4.tgz#3ad6f38368c6d4c8c75ec17623fb79aa1d071d81" - dependencies: - random-bytes "~1.0.0" - -uid-safe@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.0.0.tgz#a7f3c6ca64a1f6a5d04ec0ef3e4c3d5367317137" - dependencies: - base64-url "1.2.1" - -ultron@1.0.x: - version "1.0.2" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" - -unc-path-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - -underscore.string@3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.4.tgz#2c2a3f9f83e64762fdc45e6ceac65142864213db" - dependencies: - sprintf-js "^1.0.3" - util-deprecate "^1.0.2" - -unherit@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.0.tgz#6b9aaedfbf73df1756ad9e316dd981885840cd7d" - dependencies: - inherits "^2.0.1" - xtend "^4.0.1" - -unified@^6.0.0: - version "6.1.5" - resolved "https://registry.yarnpkg.com/unified/-/unified-6.1.5.tgz#716937872621a63135e62ced2f3ac6a063c6fb87" - dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-plain-obj "^1.1.0" - trough "^1.0.0" - vfile "^2.0.0" - x-is-function "^1.0.4" - x-is-string "^0.1.0" - -union-value@^0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-0.2.4.tgz#7375152786679057e7b37aa676e83468fc0274f0" - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^0.4.3" - -unique-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b" - -unique-stream@^2.0.2: - version "2.2.1" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369" - dependencies: - json-stable-stringify "^1.0.0" - through2-filter "^2.0.0" - -unist-builder@^1.0.0, unist-builder@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-1.0.2.tgz#8c3b9903ef64bcfb117dd7cf6a5d98fc1b3b27b6" - dependencies: - object-assign "^4.1.0" - -unist-util-generated@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.1.tgz#99f16c78959ac854dee7c615c291924c8bf4de7f" - -unist-util-is@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.1.tgz#0c312629e3f960c66e931e812d3d80e77010947b" - -unist-util-modify-children@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-1.1.1.tgz#66d7e6a449e6f67220b976ab3cb8b5ebac39e51d" - dependencies: - array-iterate "^1.0.0" - -unist-util-position@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.0.0.tgz#e6e1e03eeeb81c5e1afe553e8d4adfbd7c0d8f82" - -unist-util-remove-position@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.1.tgz#5a85c1555fc1ba0c101b86707d15e50fa4c871bb" - dependencies: - unist-util-visit "^1.1.0" - -unist-util-stringify-position@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.1.tgz#3ccbdc53679eed6ecf3777dd7f5e3229c1b6aa3c" - -unist-util-visit@^1.0.0, unist-util-visit@^1.0.1, unist-util-visit@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.1.3.tgz#ec268e731b9d277a79a5b5aa0643990e405d600b" - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - -unset-value@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-0.1.2.tgz#506810b867f27c2a5a6e9b04833631f6de58d310" - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -unzip@~0.1.9: - version "0.1.11" - resolved "https://registry.yarnpkg.com/unzip/-/unzip-0.1.11.tgz#89749c63b058d7d90d619f86b98aa1535d3b97f0" - dependencies: - binary ">= 0.3.0 < 1" - fstream ">= 0.1.30 < 1" - match-stream ">= 0.0.2 < 1" - pullstream ">= 0.4.1 < 1" - readable-stream "~1.0.31" - setimmediate ">= 1.0.1 < 2" - -urix@^0.1.0, urix@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - -url-parse@^1.0.5: - version "1.1.9" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.9.tgz#c67f1d775d51f0a18911dd7b3ffad27bb9e5bd19" - dependencies: - querystringify "~1.0.0" - requires-port "1.0.x" - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -url@~0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8" - dependencies: - define-property "^0.2.5" - isobject "^3.0.0" - lazy-cache "^2.0.2" - -user-home@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" - -useragent@^2.1.12: - version "2.2.1" - resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.2.1.tgz#cf593ef4f2d175875e8bb658ea92e18a4fd06d8e" - dependencies: - lru-cache "2.2.x" - tmp "0.0.x" - -util-deprecate@^1.0.2, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - -util@0.10.3, "util@>=0.10.3 <1", util@^0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - dependencies: - inherits "2.0.1" - -utils-merge@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" - -uuid@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" - -v8flags@^2.0.2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" - dependencies: - user-home "^1.1.1" - -vali-date@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6" - -validate-npm-package-license@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" - dependencies: - spdx-correct "~1.0.0" - spdx-expression-parse "~1.0.0" - -vargs@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/vargs/-/vargs-0.1.0.tgz#6b6184da6520cc3204ce1b407cac26d92609ebff" - -vary@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.0.1.tgz#99e4981566a286118dfb2b817357df7993376d10" - -vary@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vfile-location@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.2.tgz#d3675c59c877498e492b4756ff65e4af1a752255" - -vfile-reporter@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vfile-reporter/-/vfile-reporter-4.0.0.tgz#ea6f0ae1342f4841573985e05f941736f27de9da" - dependencies: - repeat-string "^1.5.0" - string-width "^1.0.0" - supports-color "^4.1.0" - unist-util-stringify-position "^1.0.0" - vfile-statistics "^1.1.0" - -vfile-sort@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/vfile-sort/-/vfile-sort-2.1.0.tgz#49501c9e8bbe5adff2e9b3a7671ee1b1e20c5210" - -vfile-statistics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vfile-statistics/-/vfile-statistics-1.1.0.tgz#02104c60fdeed1d11b1f73ad65330b7634b3d895" - -vfile@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-2.2.0.tgz#ce47a4fb335922b233e535db0f7d8121d8fced4e" - dependencies: - is-buffer "^1.1.4" - replace-ext "1.0.0" - unist-util-stringify-position "^1.0.0" - -vhost@~3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/vhost/-/vhost-3.0.2.tgz#2fb1decd4c466aa88b0f9341af33dc1aff2478d5" - -vinyl-fs@^0.3.0: - version "0.3.14" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-0.3.14.tgz#9a6851ce1cac1c1cea5fe86c0931d620c2cfa9e6" - dependencies: - defaults "^1.0.0" - glob-stream "^3.1.5" - glob-watcher "^0.0.6" - graceful-fs "^3.0.0" - mkdirp "^0.5.0" - strip-bom "^1.0.0" - through2 "^0.6.1" - vinyl "^0.4.0" - -vinyl-fs@^2.3.1: - version "2.4.4" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239" - dependencies: - duplexify "^3.2.0" - glob-stream "^5.3.2" - graceful-fs "^4.0.0" - gulp-sourcemaps "1.6.0" - is-valid-glob "^0.3.0" - lazystream "^1.0.0" - lodash.isequal "^4.0.0" - merge-stream "^1.0.0" - mkdirp "^0.5.0" - object-assign "^4.0.0" - readable-stream "^2.0.4" - strip-bom "^2.0.0" - strip-bom-stream "^1.0.0" - through2 "^2.0.0" - through2-filter "^2.0.0" - vali-date "^1.0.0" - vinyl "^1.0.0" - -vinyl-sourcemaps-apply@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" - dependencies: - source-map "^0.5.1" - -vinyl@1.X, vinyl@^1.0.0, vinyl@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - -vinyl@^0.2.1: - version "0.2.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.2.3.tgz#bca938209582ec5a49ad538a00fa1f125e513252" - dependencies: - clone-stats "~0.0.1" - -vinyl@^0.4.0: - version "0.4.6" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" - dependencies: - clone "^0.2.0" - clone-stats "^0.0.1" - -vinyl@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - -vinyl@^2.0.0, vinyl@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c" - dependencies: - clone "^2.1.1" - clone-buffer "^1.0.0" - clone-stats "^1.0.0" - cloneable-readable "^1.0.0" - remove-trailing-separator "^1.0.1" - replace-ext "^1.0.0" - -vlq@^0.2.1: - version "0.2.2" - resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.2.tgz#e316d5257b40b86bb43cb8d5fea5d7f54d6b0ca1" - -vm-browserify@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" - dependencies: - indexof "0.0.1" - -void-elements@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - -walk@^2.3.9: - version "2.3.9" - resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.9.tgz#31b4db6678f2ae01c39ea9fb8725a9031e558a7b" - dependencies: - foreachasync "^3.0.0" - -walkdir@^0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.0.11.tgz#a16d025eb931bd03b52f308caed0f40fcebe9532" - -watchpack@^0.2.1: - version "0.2.9" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-0.2.9.tgz#62eaa4ab5e5ba35fdfc018275626e3c0f5e3fb0b" - dependencies: - async "^0.9.0" - chokidar "^1.0.0" - graceful-fs "^4.1.2" - -watchpack@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" - dependencies: - async "^2.1.2" - chokidar "^1.7.0" - graceful-fs "^4.1.2" - -wd@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/wd/-/wd-1.4.0.tgz#85958787abc32f048d4b3927b2ab3c5fc8c9c9fa" - dependencies: - archiver "1.3.0" - async "2.0.1" - lodash "4.16.2" - mkdirp "^0.5.1" - q "1.4.1" - request "2.79.0" - underscore.string "3.3.4" - vargs "0.1.0" - -webdriverio@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/webdriverio/-/webdriverio-3.4.0.tgz#d9d4d3c31366f053e10af644b0eaad5e873ab7b5" - dependencies: - archiver "~0.14.3" - array.from "^0.2.0" - co "^4.5.4" - css-parse "~2.0.0" - css-value "~0.0.1" - deepmerge "~0.2.7" - ejs "^2.3.1" - glob "^5.0.10" - inquirer "^0.8.5" - is-generator "^1.0.2" - optimist "^0.6.1" - q "~1.3.0" - request "2.49.0" - rgb2hex "~0.1.0" - supports-color "^1.3.1" - url "~0.10.3" - wgxpath "~1.0.0" - -webpack-core@~0.6.9: - version "0.6.9" - resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.9.tgz#fc571588c8558da77be9efb6debdc5a3b172bdc2" - dependencies: - source-list-map "~0.1.7" - source-map "~0.4.1" - -webpack-dev-middleware@^1.0.11: - version "1.12.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.0.tgz#d34efefb2edda7e1d3b5dbe07289513219651709" - dependencies: - memory-fs "~0.4.1" - mime "^1.3.4" - path-is-absolute "^1.0.0" - range-parser "^1.0.3" - time-stamp "^2.0.0" - -webpack-sources@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf" - dependencies: - source-list-map "^2.0.0" - source-map "~0.5.3" - -webpack-stream@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/webpack-stream/-/webpack-stream-3.2.0.tgz#3a1d160fb11d41727b7ce6f32f722464f98b2186" - dependencies: - gulp-util "^3.0.7" - lodash.clone "^4.3.2" - lodash.some "^4.2.2" - memory-fs "^0.3.0" - through "^2.3.8" - vinyl "^1.1.0" - webpack "^1.12.9" - -webpack@^1.12.9: - version "1.15.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-1.15.0.tgz#4ff31f53db03339e55164a9d468ee0324968fe98" - dependencies: - acorn "^3.0.0" - async "^1.3.0" - clone "^1.0.2" - enhanced-resolve "~0.9.0" - interpret "^0.6.4" - loader-utils "^0.2.11" - memory-fs "~0.3.0" - mkdirp "~0.5.0" - node-libs-browser "^0.7.0" - optimist "~0.6.0" - supports-color "^3.1.0" - tapable "~0.1.8" - uglify-js "~2.7.3" - watchpack "^0.2.1" - webpack-core "~0.6.9" - -webpack@^3.0.0: - version "3.5.5" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.5.5.tgz#3226f09fc8b3e435ff781e7af34f82b68b26996c" - dependencies: - acorn "^5.0.0" - acorn-dynamic-import "^2.0.0" - ajv "^5.1.5" - ajv-keywords "^2.0.0" - async "^2.1.2" - enhanced-resolve "^3.4.0" - escope "^3.6.0" - interpret "^1.0.0" - json-loader "^0.5.4" - json5 "^0.5.1" - loader-runner "^2.3.0" - loader-utils "^1.1.0" - memory-fs "~0.4.1" - mkdirp "~0.5.0" - node-libs-browser "^2.0.0" - source-map "^0.5.3" - supports-color "^4.2.1" - tapable "^0.2.7" - uglifyjs-webpack-plugin "^0.4.6" - watchpack "^1.4.0" - webpack-sources "^1.0.1" - yargs "^8.0.2" - -websocket-driver@>=0.5.1: - version "0.6.5" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" - dependencies: - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7" - -wgxpath@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wgxpath/-/wgxpath-1.0.0.tgz#eef8a4b9d558cc495ad3a9a2b751597ecd9af690" - -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - -which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.9: - version "1.3.0" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" - dependencies: - string-width "^1.0.2" - -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - -window-size@^0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" - -window-size@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" - -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - -wordwrap@^1.0.0, wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" - dependencies: - mkdirp "^0.5.1" - -ws@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.2.tgz#8a244fa052401e08c9886cf44a85189e1fd4067f" - dependencies: - options ">=0.0.5" - ultron "1.0.x" - -wtf-8@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a" - -x-is-function@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/x-is-function/-/x-is-function-1.0.4.tgz#5d294dc3d268cbdd062580e0c5df77a391d1fa1e" - -x-is-string@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" - -xmlhttprequest-ssl@1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" - -xregexp@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" - -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" - -xtend@~2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b" - dependencies: - object-keys "~0.4.0" - -xtend@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" - -y18n@^3.2.0, y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - -yargs-parser@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" - dependencies: - camelcase "^3.0.0" - lodash.assign "^4.0.6" - -yargs-parser@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" - dependencies: - camelcase "^3.0.0" - -yargs-parser@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" - dependencies: - camelcase "^4.1.0" - -yargs@3.29.0: - version "3.29.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.29.0.tgz#1aab9660eae79d8b8f675bcaeeab6ee34c2cf69c" - dependencies: - camelcase "^1.2.1" - cliui "^3.0.3" - decamelize "^1.0.0" - os-locale "^1.4.0" - window-size "^0.1.2" - y18n "^3.2.0" - -yargs@^1.3.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-1.3.3.tgz#054de8b61f22eefdb7207059eaef9d6b83fb931a" - -yargs@^4.8.1: - version "4.8.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" - dependencies: - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - lodash.assign "^4.0.3" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.1" - which-module "^1.0.0" - window-size "^0.2.0" - y18n "^3.2.1" - yargs-parser "^2.4.1" - -yargs@^6.0.1: - version "6.6.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" - dependencies: - camelcase "^3.0.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.2" - which-module "^1.0.0" - y18n "^3.2.1" - yargs-parser "^4.2.0" - -yargs@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" - dependencies: - camelcase "^4.1.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^2.0.0" - read-pkg-up "^2.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^7.0.0" - -yargs@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0" - -yeast@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" - -zip-stream@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-1.2.0.tgz#a8bc45f4c1b49699c6b90198baacaacdbcd4ba04" - dependencies: - archiver-utils "^1.3.0" - compress-commons "^1.2.0" - lodash "^4.8.0" - readable-stream "^2.0.0" - -zip-stream@~0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-0.5.2.tgz#32dcbc506d0dab4d21372625bd7ebaac3c2fff56" - dependencies: - compress-commons "~0.2.0" - lodash "~3.2.0" - readable-stream "~1.0.26"